Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
3ed735f
callbacks from C work
Buggaboo Dec 25, 2019
8a6bc25
clean up
Buggaboo Dec 25, 2019
502edad
Merge branch 'dev' into feature/future-stream
Buggaboo Mar 30, 2020
a5d177e
fixed tests, now using futures
Buggaboo May 17, 2020
372de28
Callbacks are pretty useless if we don't know which
Buggaboo May 22, 2020
f19c4e2
Merge branch 'dev' into feature/future-stream
Buggaboo Jul 16, 2020
bd6f3ee
Some Stream tests won't work without an 'await', ffi blows up
Buggaboo Jul 17, 2020
70a038c
improve sub and unsub behavior
Buggaboo Jul 17, 2020
b3e5fda
update readme for streams
Buggaboo Jul 17, 2020
113fb55
removed double quotes, also unnecessary "this"
Buggaboo Jul 30, 2020
3a6184d
removed superfluous extraneous redundant parameterized typedefs
Buggaboo Sep 24, 2020
0c79ef0
Merge branch 'main' into feature/future-stream
Buggaboo Sep 24, 2020
e00bdfd
double quotes to single
Buggaboo Sep 26, 2020
2a4c6f8
specified the types properly for the compiler on obj creation, instea…
Buggaboo Sep 26, 2020
80b1256
Code duplicated for the sake of shutting up a lint false positive:
Buggaboo Sep 26, 2020
6aacbf9
fix in case single observer for multiple stores
Buggaboo Sep 28, 2020
5d69ebf
applied stream to demo app and flattened the code a bit
Buggaboo Sep 28, 2020
3c9698f
the sync tests just required more Future.delayed(... 0))
Buggaboo Sep 28, 2020
b3483ed
forgot to close the query
Buggaboo Sep 28, 2020
63600bb
package private fields
Buggaboo Sep 29, 2020
e62ad2a
replaced listener with StreamBuilder, extracted database plumbing to …
Buggaboo Sep 29, 2020
cd4a391
oopsie
Buggaboo Sep 29, 2020
298169e
changed comments to just declaring something explodes
Buggaboo Oct 5, 2020
c32df43
Merge branch 'main' into feature/future-stream
greenrobot-team Oct 13, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,37 @@ scoreQuery.close();
query.close();
```

### Streams

Streams can be created from queries.
The streams can be extended with [rxdart](https://github.com/ReactiveX/rxdart);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason why rxdart? Are there other alternatives you've considered?

Copy link
Contributor Author

@Buggaboo Buggaboo Jul 17, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to be the most popular one, and it supports Stream which is more idiomatic, by way of extensions. I expose a stream property from an observable query for that reason.


```dart
import "package:objectbox/src/observable.dart";

// final store = ...
final query = box.query(condition).build();
final queryStream = query.stream;
final sub1 = queryStream.listen((query) {
print(query.count());
});

// box.put ...

sub1.cancel();

final stream = query.findStream(limit:5);
final sub2 = stream.listen((list) {
// ...
});

// clean up
sub2.cancel();
store.unsubscribe();

store.close();
```

Help wanted
-----------
ObjectBox for Dart is still in an early stage with limited feature set (compared to other languages).
Expand Down
232 changes: 132 additions & 100 deletions example/flutter/objectbox_demo/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import 'package:objectbox/objectbox.dart';
import 'package:intl/intl.dart';
import 'package:path_provider/path_provider.dart';
import 'objectbox.g.dart';
import 'package:objectbox/src/observable.dart';
import 'dart:async';
import 'dart:io';

@Entity()
class Note {
Expand All @@ -11,14 +14,17 @@ class Note {

String text;
String comment;
int date; // TODO: use DateTime class
int date;

Note();

Note.construct(this.text) {
date = DateTime.now().millisecondsSinceEpoch;
print('constructed date: $date');
}

get dateFormat => DateFormat('dd.MM.yyyy hh:mm:ss')
.format(DateTime.fromMillisecondsSinceEpoch(date));
}

void main() => runApp(MyApp());
Expand All @@ -43,143 +49,169 @@ class MyHomePage extends StatefulWidget {
_MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
final _noteInputController = TextEditingController();
class ViewModel {
Store _store;
Box<Note> _box;
List<Note> _notes = [];
Query<Note> _query;

ViewModel(Directory dir) {
_store = Store(getObjectBoxModel(), directory: dir.path + '/objectbox');
_box = Box<Note>(_store);

final dateProp = Note_.date;
final dummyCondition = dateProp.greaterThan(0);

_query = _box
.query(dummyCondition)
.order(dateProp, flags: Order.descending)
.build();
}

void addNote(Note note) => _box.put(note);

void removeNote(Note note) => _box.remove(note.id);

get queryStream => _query.findStream();

get allNotes => _query.find();

void dispose() {
_query.close();
_store.unsubscribe();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this should be part of close(). This feels like something very easy to forget.

Copy link
Contributor Author

@Buggaboo Buggaboo Oct 5, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's called by close iirc. I didn't want to expose the BLoC/VM's internals to the Widgets. Leaky abstractions and such.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, sorry, should have been more clear: was talking about not even exposing unsubscribe() to the user and doing it as part of close().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point.

_store.close();
}
}

class _MyHomePageState extends State<MyHomePage> {
final _noteInputController = TextEditingController();
final _listController = StreamController<List<Note>>(sync:true);
Stream<List<Note>> _stream;
ViewModel _vm;

void _addNote() {
if (_noteInputController.text.isEmpty) return;
final newNote = Note.construct(_noteInputController.text);
newNote.id = _box.put(newNote);
setState(() => _notes.add(newNote));
_vm.addNote(Note.construct(_noteInputController.text));
_noteInputController.text = '';
}

void _removeNote(int index) {
_box.remove(_notes[index].id);
setState(() => _notes.removeAt(index));
}

@override
void initState() {
super.initState();

getApplicationDocumentsDirectory().then((dir) {
_store = Store(getObjectBoxModel(), directory: dir.path + '/objectbox');
_box = Box<Note>(_store);
final notesFromDb = _box.getAll();
setState(() => _notes = notesFromDb);
// TODO: don't show UI before this point
_vm = ViewModel(dir);
_stream = _listController.stream;

setState(() {});

_listController.add(_vm.allNotes);
_listController.addStream(_vm.queryStream);
});
}

@override
void dispose() {
_noteInputController.dispose();
_listController.close();
_vm.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.all(20.0),
child: Row(
children: <Widget>[
Expanded(
GestureDetector Function(BuildContext, int) _itemBuilder(List<Note> notes) {
return (BuildContext context, int index) {
return GestureDetector(
onTap: () => _vm.removeNote(notes[index]),
child: Row(
children: <Widget>[
Expanded(
child: Container(
child: Padding(
padding:
EdgeInsets.symmetric(vertical: 18.0, horizontal: 10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: EdgeInsets.only(right: 10.0),
child: TextField(
decoration:
InputDecoration(hintText: 'Enter new note'),
controller: _noteInputController,
Text(
notes[index].text,
style: TextStyle(
fontSize: 15.0,
),
),
Padding(
padding: EdgeInsets.only(top: 10.0, right: 10.0),
child: Align(
alignment: Alignment.centerRight,
child: Text(
'Click a note to remove it',
style: TextStyle(
fontSize: 11.0,
color: Colors.grey,
),
padding: EdgeInsets.only(top: 5.0),
child: Text(
'Added on ${notes[index].dateFormat}',
style: TextStyle(
fontSize: 12.0,
),
),
),
],
),
),
Column(
children: <Widget>[
RaisedButton(
onPressed: _addNote,
child: Text('Add'),
)
],
)
],
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: Colors.black12))),
),
),
),
Expanded(
child: ListView.builder(
shrinkWrap: true,
padding: EdgeInsets.symmetric(horizontal: 20.0),
itemCount: _notes.length,
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
onTap: () => _removeNote(index),
child: Row(
children: <Widget>[
Expanded(
child: Container(
child: Padding(
padding: EdgeInsets.symmetric(
vertical: 18.0, horizontal: 10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
_notes[index].text,
style: TextStyle(
fontSize: 15.0,
),
),
Padding(
padding: EdgeInsets.only(top: 5.0),
child: Text(
"Added on ${DateFormat('dd.MM.yyyy hh:mm:ss').format(DateTime.fromMillisecondsSinceEpoch(_notes[index].date))}",
style: TextStyle(
fontSize: 12.0,
),
),
),
],
),
],
),
);
};
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Column(children: <Widget>[
Padding(
padding: EdgeInsets.all(20.0),
child: Row(
children: <Widget>[
Expanded(
child: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(horizontal: 10.0),
child: TextField(
decoration:
InputDecoration(hintText: 'Enter a new note'),
controller: _noteInputController,
onSubmitted: (String) => _addNote()
),
),
Padding(
padding: EdgeInsets.only(top: 10.0, right: 10.0),
child: Align(
alignment: Alignment.centerRight,
child: Text(
'Tap a note to remove it',
style: TextStyle(
fontSize: 11.0,
color: Colors.grey,
),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(color: Colors.black12))),
),
),
],
),
);
},
),
),
],
),
)
],
),
],
),
),
Expanded(
child: StreamBuilder<List<Note>>(
stream: _stream,
builder: (context, snapshot) {
return ListView.builder(
shrinkWrap: true,
padding: EdgeInsets.symmetric(horizontal: 20.0),
itemCount: snapshot.hasData ? snapshot.data.length : 0,
itemBuilder: _itemBuilder(snapshot.data));
}))
]),
);
}
}
10 changes: 10 additions & 0 deletions lib/src/bindings/bindings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,11 @@ class _ObjectBoxBindings {

obx_query_visit_dart_t obx_query_visit;

// Observers
obx_observe_t obx_observe;
obx_observe_single_type_t<int> obx_observe_single_type;
obx_observer_close_dart_t obx_observer_close;

// query property
obx_query_prop_t<int> obx_query_prop;
obx_query_prop_close_t<int> obx_query_prop_close;
Expand Down Expand Up @@ -509,6 +514,11 @@ class _ObjectBoxBindings {
obx_query_visit =
_fn<obx_query_visit_native_t>('obx_query_visit').asFunction();

// observers
obx_observe = _fn<obx_observe_t>('obx_observe').asFunction();
obx_observe_single_type = _fn<obx_observe_single_type_t<Uint32>>('obx_observe_single_type').asFunction();
obx_observer_close = _fn<obx_observer_close_native_t>('obx_observer_close').asFunction();

// query property
obx_query_prop =
_fn<obx_query_prop_t<Uint32>>('obx_query_prop').asFunction();
Expand Down
11 changes: 11 additions & 0 deletions lib/src/bindings/signatures.dart
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,17 @@ typedef obx_query_visit_dart_t = int Function(
Pointer<NativeFunction<obx_data_visitor_native_t>> visitor,
Pointer<Void> user_data);

// observers

typedef obx_observer_t = Void Function(Pointer<Void> user_data, Pointer<Uint32> entity_id, Uint32 type_ids_count);
typedef obx_observer_single_type_native_t = Void Function(Pointer<Void> user_data);
typedef obx_observer_single_type_dart_t = void Function(Pointer<Void> user_data);

typedef obx_observe_t = Pointer<Void> Function(Pointer<Void> store, Pointer<NativeFunction<obx_observer_t>> callback, Pointer<Void> user_data);
typedef obx_observe_single_type_t<T> = Pointer<Void> Function(Pointer<Void> store, T entity_id, Pointer<NativeFunction<obx_observer_single_type_native_t>> callback, Pointer<Void> user_data);
typedef obx_observer_close_native_t = Void Function(Pointer<Void> observer);
typedef obx_observer_close_dart_t = void Function(Pointer<Void> observer);

// query property

typedef obx_query_prop_t<T> = Pointer<Void> Function(
Expand Down
Loading