diff --git a/objectbox/example/flutter/objectbox_demo_sync/README.md b/objectbox/example/flutter/objectbox_demo_sync/README.md index 6e157138..b6b04f03 100644 --- a/objectbox/example/flutter/objectbox_demo_sync/README.md +++ b/objectbox/example/flutter/objectbox_demo_sync/README.md @@ -1,9 +1,24 @@ -# objectbox_demo +# objectbox_demo_sync ## Getting Started with ObjectBox Sync in Flutter / Dart -This project contains the Flutter version of the main example from the [objectbox-examples](https://github.com/objectbox/objectbox-examples) repository. +This project contains the Flutter version of the Sync example from the [objectbox-examples](https://github.com/objectbox/objectbox-examples) repository. You need to have a Sync Trial and run the Sync Server to run the demo --> Apply for the [Sync Trial here](https://objectbox.io/sync/). Also, do check out the [Sync Docs](https://sync.objectbox.io/). + +The basic steps to get this demo running (assuming you have a working Flutter setup): + +``` +# Set up project, get dependencies +flutter pub get + +# Generate model and code files for ObjectBox +flutter pub run build_runner build + +# Run the app in debug mode +flutter run +``` + +Optional: depending on your setup you might have to adjust the Sync server address in [objectbox.dart](/lib/objectbox.dart). diff --git a/objectbox/example/flutter/objectbox_demo_sync/lib/main.dart b/objectbox/example/flutter/objectbox_demo_sync/lib/main.dart index 37e2fc5e..8f912adc 100644 --- a/objectbox/example/flutter/objectbox_demo_sync/lib/main.dart +++ b/objectbox/example/flutter/objectbox_demo_sync/lib/main.dart @@ -41,55 +41,77 @@ class MyHomePage extends StatefulWidget { } class _MyHomePageState extends State { - final _noteInputController = TextEditingController(); + final _textInputController = TextEditingController(); - Future _addNote() async { - if (_noteInputController.text.isEmpty) return; - await objectbox.addNote(_noteInputController.text); - _noteInputController.text = ''; + Future _addTask() async { + if (_textInputController.text.isEmpty) return; + await objectbox.addTask(_textInputController.text); + _textInputController.text = ''; } @override void dispose() { - _noteInputController.dispose(); + _textInputController.dispose(); super.dispose(); } - GestureDetector Function(BuildContext, int) _itemBuilder(List notes) => - (BuildContext context, int index) => GestureDetector( - onTap: () => objectbox.removeNote(notes[index].id), + Widget Function(BuildContext, int) _itemBuilder(List tasks) => + (BuildContext context, int index) => Dismissible( + background: Container(color: Colors.red), + key: UniqueKey(), + onDismissed: (direction) { + objectbox.removeTask(tasks[index].id); + // List updated via watched query stream. + }, child: Row( children: [ Expanded( child: Container( + // draw bottom border decoration: const BoxDecoration( border: Border(bottom: BorderSide(color: Colors.black12))), - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 18.0, horizontal: 10.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - notes[index].text, - style: const TextStyle( - fontSize: 15.0, - ), - // Provide a Key for the integration test - key: Key('list_item_$index'), - ), - Padding( - padding: const EdgeInsets.only(top: 5.0), - child: Text( - 'Added on ${notes[index].dateFormat}', - style: const TextStyle( - fontSize: 12.0, + padding: const EdgeInsets.symmetric( + vertical: 18.0, horizontal: 10.0), + child: Row( + children: [ + Checkbox( + value: tasks[index].isFinished(), + onChanged: (bool? value) { + // not tri-state, so value is never null + objectbox.changeTaskFinished( + tasks[index], value!); + // List updated via watched query stream. + }), + Container( + padding: const EdgeInsets.only(left: 16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + tasks[index].text, + // strike-through text style for finished tasks + style: tasks[index].isFinished() + ? const TextStyle( + color: Colors.grey, + decoration: TextDecoration.lineThrough) + : const TextStyle(fontSize: 15.0), + // Provide a Key for the integration test + key: Key('list_item_$index'), ), - ), + Padding( + padding: const EdgeInsets.only(top: 5.0), + child: Text( + tasks[index].getStateText(), + style: const TextStyle( + fontSize: 12.0, + ), + ), + ), + ], ), - ], - ), + ), + ], ), ), ), @@ -113,10 +135,10 @@ class _MyHomePageState extends State { Padding( padding: const EdgeInsets.symmetric(horizontal: 10.0), child: TextField( - decoration: const InputDecoration( - hintText: 'Enter a new note'), - controller: _noteInputController, - onSubmitted: (value) => _addNote(), + decoration: + const InputDecoration(hintText: 'Enter new task'), + controller: _textInputController, + onSubmitted: (value) => _addTask(), // Provide a Key for the integration test key: const Key('input'), ), @@ -126,7 +148,7 @@ class _MyHomePageState extends State { child: Align( alignment: Alignment.centerRight, child: Text( - 'Tap a note to remove it', + 'Delete a task by swiping it', style: TextStyle( fontSize: 11.0, color: Colors.grey, @@ -141,8 +163,8 @@ class _MyHomePageState extends State { ), ), Expanded( - child: StreamBuilder>( - stream: objectbox.getNotes(), + child: StreamBuilder>( + stream: objectbox.getTasks(), builder: (context, snapshot) => ListView.builder( shrinkWrap: true, padding: const EdgeInsets.symmetric(horizontal: 20.0), @@ -154,7 +176,7 @@ class _MyHomePageState extends State { // See https://github.com/flutter/flutter/issues/9383 floatingActionButton: FloatingActionButton( key: const Key('submit'), - onPressed: _addNote, + onPressed: _addTask, child: const Icon(Icons.add), ), ); diff --git a/objectbox/example/flutter/objectbox_demo_sync/lib/model.dart b/objectbox/example/flutter/objectbox_demo_sync/lib/model.dart index 5ee9448c..c5adfa8e 100644 --- a/objectbox/example/flutter/objectbox_demo_sync/lib/model.dart +++ b/objectbox/example/flutter/objectbox_demo_sync/lib/model.dart @@ -7,17 +7,51 @@ import 'objectbox.g.dart'; @Entity() @Sync() -class Note { +class Task { int id; String text; - String? comment; - /// Note: Stored in milliseconds without time zone info. - DateTime date; + /// Note: DateTime is stored in milliseconds without time zone info. + @Property(type: PropertyType.date) + DateTime dateCreated; - Note(this.text, {this.id = 0, this.comment, DateTime? date}) - : date = date ?? DateTime.now(); + @Property(type: PropertyType.date) + DateTime dateFinished; - String get dateFormat => DateFormat('dd.MM.yyyy hh:mm:ss').format(date); + /// Create task with the given text at the current time. + Task(this.text, {this.id = 0, DateTime? dateCreated, DateTime? dateFinished}) + : dateCreated = dateCreated ?? DateTime.now(), + dateFinished = dateFinished ?? DateTime.fromMicrosecondsSinceEpoch(0); + + bool isFinished() { + return dateFinished.millisecondsSinceEpoch != 0; + } + + void setIsFinished(bool isFinished) { + if (isFinished) { + dateFinished = DateTime.now(); + } else { + dateFinished = DateTime.fromMicrosecondsSinceEpoch(0); + } + } + + String get dateCreatedFormat => + DateFormat('dd.MM.yy HH:mm:ss').format(dateCreated); + + String get dateFinishedFormat => + DateFormat('dd.MM.yy HH:mm:ss').format(dateFinished); + + /// If the task is new returns 'Created on ', + /// if it is finished 'Finished on '. The date is formatted + /// for the current locale. + String getStateText() { + String text; + if (isFinished()) { + text = 'Finished on $dateFinishedFormat'; + } else { + text = 'Created on $dateCreatedFormat'; + } + return text; + } } diff --git a/objectbox/example/flutter/objectbox_demo_sync/lib/objectbox-model.json b/objectbox/example/flutter/objectbox_demo_sync/lib/objectbox-model.json index f748599e..cd24f383 100644 --- a/objectbox/example/flutter/objectbox_demo_sync/lib/objectbox-model.json +++ b/objectbox/example/flutter/objectbox_demo_sync/lib/objectbox-model.json @@ -4,37 +4,37 @@ "_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.", "entities": [ { - "id": "1:2802681814019499133", - "lastPropertyId": "4:6451339597165131221", - "name": "Note", + "id": "1:6645479796472661392", + "lastPropertyId": "5:6240065879507520219", + "name": "Task", "flags": 2, "properties": [ { - "id": "1:3178873177797362769", + "id": "1:9211738071025439652", "name": "id", "type": 6, "flags": 1 }, { - "id": "2:4285343053028527696", + "id": "2:8804670454579230281", "name": "text", "type": 9 }, { - "id": "3:2606273611209948020", - "name": "comment", - "type": 9 + "id": "4:1260602348787983453", + "name": "dateCreated", + "type": 10 }, { - "id": "4:6451339597165131221", - "name": "date", + "id": "5:6240065879507520219", + "name": "dateFinished", "type": 10 } ], "relations": [] } ], - "lastEntityId": "1:2802681814019499133", + "lastEntityId": "1:6645479796472661392", "lastIndexId": "0:0", "lastRelationId": "0:0", "lastSequenceId": "0:0", diff --git a/objectbox/example/flutter/objectbox_demo_sync/lib/objectbox.dart b/objectbox/example/flutter/objectbox_demo_sync/lib/objectbox.dart index fc35e531..01db16d2 100644 --- a/objectbox/example/flutter/objectbox_demo_sync/lib/objectbox.dart +++ b/objectbox/example/flutter/objectbox_demo_sync/lib/objectbox.dart @@ -13,11 +13,11 @@ class ObjectBox { /// The Store of this app. late final Store _store; - /// A Box of notes. - late final Box _noteBox; + /// A Box of tasks. + late final Box _taskBox; ObjectBox._create(this._store) { - _noteBox = Box(_store); + _taskBox = _store.box(); // TODO configure actual sync server address and authentication // For configuration and docs, see objectbox/lib/src/sync.dart @@ -44,10 +44,11 @@ class ObjectBox { return ObjectBox._create(store); } - Stream> getNotes() { - // Query for all notes, sorted by their date. + Stream> getTasks() { + // Query for all tasks, sorted by their date. // https://docs.objectbox.io/queries - final builder = _noteBox.query().order(Note_.date, flags: Order.descending); + final builder = + _taskBox.query().order(Task_.dateCreated, flags: Order.descending); // Build and watch the query, // set triggerImmediately to emit the query immediately on listen. return builder @@ -56,13 +57,18 @@ class ObjectBox { .map((query) => query.find()); } - /// Add a note. + /// Add a task. /// /// To avoid frame drops, run ObjectBox operations that take longer than a /// few milliseconds, e.g. putting many objects, asynchronously. /// For this example only a single object is put which would also be fine if /// done using [Box.put]. - Future addNote(String text) => _noteBox.putAsync(Note(text)); + Future addTask(String text) => _taskBox.putAsync(Task(text)); - Future removeNote(int id) => _noteBox.removeAsync(id); + Future removeTask(int id) => _taskBox.removeAsync(id); + + Future changeTaskFinished(Task task, bool isFinished) { + task.setIsFinished(isFinished); + return _taskBox.putAsync(task); + } }