Skip to content

Commit c9daa5b

Browse files
chore(dataStore): Update datastore API docs and fix race conditions in tests (#1596)
* chore: add delay to observe and observeQuery tests * chore: update api docs for datastore
1 parent 2650932 commit c9daa5b

File tree

4 files changed

+89
-15
lines changed

4 files changed

+89
-15
lines changed

packages/amplify/amplify_flutter/lib/src/categories/amplify_datastore_category.dart

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,15 @@
1515

1616
part of amplify_interface;
1717

18-
/// Interface for DataStore category. This expose all the APIs that
19-
/// are supported by this category's plugins. This class will accept plugins to
20-
/// be registered and configured and then subsequent API calls will be forwarded
21-
/// to those plugins.
18+
/// {@template amplify_flutter.amplify_datastore_category}
19+
/// Interface for the DataStore category.
20+
///
21+
/// This exposes all the APIs that are supported by this category's plugins.
22+
/// This class will accept plugins to be registered and configured and then
23+
/// subsequent API calls will be forwarded to those plugins.
24+
/// {@endtemplate}
2225
class DataStoreCategory {
23-
/// Default constant constructor
26+
/// {@macro amplify_flutter.amplify_datastore_category}
2427
const DataStoreCategory();
2528

2629
/// Added DataStore plugins.
@@ -66,7 +69,9 @@ class DataStoreCategory {
6669
}
6770

6871
/// Query the DataStore to find all items of the specified [modelType] that satisfy the specified
69-
/// query predicate [where]. Returned items are paginated by [pagination] and sorted by [sortBy].
72+
/// query predicate [where].
73+
///
74+
/// Returned items are paginated by [pagination] and sorted by [sortBy].
7075
Future<List<T>> query<T extends Model>(ModelType<T> modelType,
7176
{QueryPredicate? where,
7277
QueryPagination? pagination,
@@ -99,17 +104,25 @@ class DataStoreCategory {
99104
: throw _pluginNotAddedException('DataStore');
100105
}
101106

102-
/// Stops the underlying DataStore, resetting the plugin to the initialized state, and deletes all data
103-
/// from the local device. Remotely synced data can be re-synced back when starting DataStore using
104-
/// [start]. local-only data will be lost permanently.
107+
/// Stops the underlying DataStore's synchronization with a remote system, if DataStore is configured to
108+
/// support remote synchronization, resetting the plugin to the initialized state, and deletes all data
109+
/// from the local device.
110+
///
111+
/// Any items pending synchronization in the outbound queue will be lost. Remotely synced data can be re-synced
112+
/// back when starting DataStore using [start]. **Local-only data will be lost permanently.**
113+
///
114+
/// Synchronization processes will be restarted on the next interaction with the DataStore, or can be
115+
/// restarted manually by calling [start].
105116
Future<void> clear() {
106117
return plugins.length == 1
107118
? plugins[0].clear()
108119
: throw _pluginNotAddedException('DataStore');
109120
}
110121

111122
/// Starts the DataStore's synchronization with a remote system, if DataStore is configured to support
112-
/// remote synchronization. This only needs to be called if you wish to start the synchronization eagerly.
123+
/// remote synchronization.
124+
///
125+
/// This only needs to be called if you wish to start the synchronization eagerly.
113126
/// If you don't call start(), the synchronization will start automatically, prior to executing any other
114127
/// operations (query, save, delete, update).
115128
Future<void> start() {
@@ -120,16 +133,19 @@ class DataStoreCategory {
120133

121134
/// Stops the underlying DataStore's synchronization with a remote system, if DataStore is configured to
122135
/// support remote synchronization.
136+
///
137+
/// Synchronization processes will be restarted on the next interaction with the DataStore, or can be
138+
/// restarted manually by calling [start].
123139
Future<void> stop() {
124140
return plugins.length == 1
125141
? plugins[0].stop()
126142
: throw _pluginNotAddedException('DataStore');
127143
}
128144

129-
/// Observe the result set of a given Query
145+
/// Observe the result set of a given Query.
130146
///
131147
/// Emits an initial [QuerySnapshot] with data from the local store, as well as
132-
/// subsequent events with data synced over the network
148+
/// subsequent events with data synced over the network.
133149
Stream<QuerySnapshot<T>> observeQuery<T extends Model>(
134150
ModelType<T> modelType, {
135151
QueryPredicate? where,

packages/amplify_datastore/example/integration_test/observe_query_test.dart

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ void main() {
2727
setUp(() async {
2828
await configureDataStore();
2929
await clearDataStore();
30+
await waitForObserve();
3031
});
3132

3233
testWidgets(
@@ -59,14 +60,18 @@ void main() {
5960
Blog.classType,
6061
).map((event) => event.items);
6162

63+
// should emit an initial snapshot with the data in the store
64+
final firstSnapshot = await observeQueryItemStream.first;
65+
expect(firstSnapshot, orderedEquals(blogs));
66+
6267
Blog newBlog1 = Blog(name: 'new blog 1');
6368
Blog newBlog2 = Blog(name: 'new blog 2');
6469
Blog newBlog1Copy = newBlog1.copyWith(name: 'new name');
6570

71+
// should emit a snapshot for each save/update
6672
expectLater(
6773
observeQueryItemStream,
6874
emitsInOrder([
69-
orderedEquals([...blogs]),
7075
orderedEquals([...blogs, newBlog1]),
7176
orderedEquals([...blogs, newBlog1, newBlog2]),
7277
orderedEquals([...blogs, newBlog1Copy, newBlog2]),
@@ -92,15 +97,19 @@ void main() {
9297
where: Blog.NAME.contains('blog'),
9398
).map((event) => event.items);
9499

100+
// should emit an initial snapshot with the data in the store
101+
final firstSnapshot = await observeQueryItemStream.first;
102+
expect(firstSnapshot, orderedEquals(blogs));
103+
95104
Blog newBlog1 = Blog(name: 'new blog 1');
96105
Blog newBlog2 = Blog(name: 'new blog 2');
97106
Blog newBlog3 = Blog(name: 'new 3');
98107
Blog newBlog1Copy = newBlog1.copyWith(name: 'new name');
99108

109+
// should emit a snapshot for each save/update
100110
expectLater(
101111
observeQueryItemStream,
102112
emitsInOrder([
103-
orderedEquals([...blogs]),
104113
orderedEquals([...blogs, newBlog1]),
105114
orderedEquals([...blogs, newBlog1, newBlog2]),
106115
orderedEquals([...blogs, newBlog2]),
@@ -125,14 +134,18 @@ void main() {
125134
sortBy: [Blog.NAME.ascending()],
126135
).map((event) => event.items);
127136

137+
// should emit an initial snapshot with the data in the store
138+
final firstSnapshot = await observeQueryItemStream.first;
139+
expect(firstSnapshot, orderedEquals(blogs));
140+
128141
Blog newBlog1 = Blog(name: 'aaa blog');
129142
Blog newBlog2 = Blog(name: 'ccc blog');
130143
Blog newBlog2Copy = newBlog2.copyWith(name: 'azz blog');
131144

145+
// should emit a snapshot for each save/update
132146
expectLater(
133147
observeQueryItemStream,
134148
emitsInOrder([
135-
orderedEquals([...blogs]),
136149
orderedEquals([newBlog1, ...blogs]),
137150
orderedEquals([newBlog1, ...blogs, newBlog2]),
138151
orderedEquals([newBlog1, newBlog2Copy, ...blogs]),

packages/amplify_datastore/example/integration_test/observe_test.dart

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,22 @@ void main() {
2828
setUp(() async {
2929
await configureDataStore();
3030
await clearDataStore();
31+
await waitForObserve();
32+
});
33+
34+
testWidgets('should emit an event for each item saved',
35+
(WidgetTester tester) async {
36+
var itemStream = Amplify.DataStore.observe(Blog.classType)
37+
.map((event) => event.item.name);
38+
39+
expectLater(
40+
itemStream,
41+
emitsInOrder(['blog 1', 'blog 2', 'blog 3']),
42+
);
43+
44+
await Amplify.DataStore.save(Blog(name: 'blog 1'));
45+
await Amplify.DataStore.save(Blog(name: 'blog 2'));
46+
await Amplify.DataStore.save(Blog(name: 'blog 3'));
3147
});
3248

3349
testWidgets('should broadcast events for create, update, and delete',

packages/amplify_datastore/example/integration_test/utils/setup_utils.dart

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const ENABLE_CLOUD_SYNC =
2626
const DATASTORE_READY_EVENT_TIMEOUT = const Duration(minutes: 2);
2727
const DELAY_TO_START_DATASTORE = const Duration(milliseconds: 500);
2828
const DELAY_TO_CLEAR_DATASTORE = const Duration(seconds: 2);
29+
const DELAY_FOR_OBSERVE = const Duration(milliseconds: 100);
2930

3031
/// Configure [AmplifyDataStore] plugin with given [modelProvider].
3132
/// When [ENABLE_CLOUD_SYNC] environment variable is set to true, it also
@@ -62,6 +63,34 @@ Future<void> clearDataStore() async {
6263
await Amplify.DataStore.clear();
6364
}
6465

66+
/// Waits for observe to be set up properly.
67+
///
68+
/// There is a bug in observe that causes events to be missed when save/delete is called
69+
/// immediately after. This work around allows the datastore tests that use observe to still run.
70+
///
71+
/// See: https://github.com/aws-amplify/amplify-flutter/issues/1590
72+
Future<void> waitForObserve() async {
73+
bool isObserveSetUp = false;
74+
final blog = Blog(name: 'TEST BLOG - Used to wait for observe to start');
75+
76+
// set up observe subscription and set isObserveSetUp to true when the first update event comes in
77+
Amplify.DataStore.observe(Blog.classType)
78+
.where((event) => event.item.id == blog.id)
79+
.first
80+
.then((event) {
81+
isObserveSetUp = true;
82+
});
83+
84+
// update in a loop until observe is set up
85+
while (!isObserveSetUp) {
86+
await Future.delayed(DELAY_FOR_OBSERVE);
87+
await Amplify.DataStore.save(blog);
88+
}
89+
90+
// clean up blog
91+
await Amplify.DataStore.delete(blog);
92+
}
93+
6594
/// Am async operator that starts DataStore API sync.
6695
/// It returns a Future which complets when [Amplify.Hub] receives the ready
6796
/// event on [HubChannel.DataStore], or completes with an timeout error if

0 commit comments

Comments
 (0)