Skip to content

Commit c48f62d

Browse files
fix(datastore): emit observeQuery snapshot when model create mutation results in an updated model (#4084)
chore: emit snapshot when create results in an update
1 parent 5c44b8b commit c48f62d

File tree

4 files changed

+105
-29
lines changed

4 files changed

+105
-29
lines changed

packages/amplify_core/lib/src/types/datastore/models/query_snapshot.dart

Lines changed: 48 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -71,37 +71,60 @@ class QuerySnapshot<T extends Model> {
7171
QuerySnapshot<T> withSubscriptionEvent({
7272
required SubscriptionEvent<T> event,
7373
}) {
74-
final sortedListCopy = SortedList<T>.from(_sortedList);
7574
SortedList<T>? updatedSortedList;
7675

7776
final newItem = event.item;
78-
final newItemMatchesPredicate = where == null || where!.evaluate(newItem);
79-
final currentItemIndex =
77+
final matchesPredicate = where == null || where!.evaluate(newItem);
78+
final currentIndex =
8079
// TODO(HuiSF): remove the ignore when merging CPK feature commits
8180
// ignore: deprecated_member_use_from_same_package
82-
sortedListCopy.indexWhere((item) => item.getId() == newItem.getId());
83-
final currentItem =
84-
currentItemIndex == -1 ? null : sortedListCopy[currentItemIndex];
85-
final currentItemMatchesPredicate =
86-
currentItem != null && (where == null || where!.evaluate(currentItem));
81+
_sortedList.indexWhere((item) => item.getId() == newItem.getId());
82+
final currentItem = currentIndex == -1 ? null : _sortedList[currentIndex];
8783

88-
if (event.eventType == EventType.create &&
89-
newItemMatchesPredicate &&
90-
currentItem == null) {
91-
updatedSortedList = sortedListCopy..addSorted(newItem);
92-
} else if (event.eventType == EventType.delete && currentItem != null) {
93-
updatedSortedList = sortedListCopy..removeAt(currentItemIndex);
94-
} else if (event.eventType == EventType.update) {
95-
if (currentItemMatchesPredicate &&
96-
newItemMatchesPredicate &&
97-
currentItem != newItem) {
98-
updatedSortedList = sortedListCopy
99-
..updateAtSorted(currentItemIndex, newItem);
100-
} else if (currentItemMatchesPredicate && !newItemMatchesPredicate) {
101-
updatedSortedList = sortedListCopy..removeAt(currentItemIndex);
102-
} else if (currentItem == null && newItemMatchesPredicate) {
103-
updatedSortedList = sortedListCopy..addSorted(newItem);
104-
}
84+
switch (event.eventType) {
85+
case EventType.create:
86+
// Skip any new item that doesn't match the predicate.
87+
if (!matchesPredicate) break;
88+
if (currentItem == null) {
89+
// Add the item to the list. This is a new item and matches the
90+
// predicate, it should be added.
91+
updatedSortedList = _sortedList.copy()..addSorted(newItem);
92+
} else if (currentItem != newItem) {
93+
// Update the item in the list. This is a "new" item, but it already
94+
// exists in the list with a different value. This is the result of
95+
// the item being created on this device and App Sync returning an
96+
// updated item during the create mutation. This can happen when using
97+
// custom resolvers.
98+
updatedSortedList = _sortedList.copy()
99+
..updateAtSorted(
100+
currentIndex,
101+
newItem,
102+
);
103+
}
104+
case EventType.update:
105+
if (currentItem == null && matchesPredicate) {
106+
// Add the item to the list. This is an existing item that matches the
107+
// predicate but is not yet in the list.
108+
updatedSortedList = _sortedList.copy()..addSorted(newItem);
109+
} else if (currentItem != newItem && matchesPredicate) {
110+
// Update the item in the list. This item exists in the list but the
111+
// value of the item has changed.
112+
updatedSortedList = _sortedList.copy()
113+
..updateAtSorted(
114+
currentIndex,
115+
newItem,
116+
);
117+
} else if (currentItem != null && !matchesPredicate) {
118+
// Remove the item from the list. The item exist in the list but no
119+
// longer matches the predicate.
120+
updatedSortedList = _sortedList.copy()..removeAt(currentIndex);
121+
}
122+
case EventType.delete:
123+
if (currentItem != null) {
124+
// Remove the item from the list. The item exists in the list but was
125+
// just deleted.
126+
updatedSortedList = _sortedList.copy()..removeAt(currentIndex);
127+
}
105128
}
106129
if (updatedSortedList != null) {
107130
return QuerySnapshot._(

packages/amplify_core/lib/src/types/datastore/models/sorted_list.dart

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,6 @@ class SortedList<E> with ListMixin<E> {
2525
}) : _items = items,
2626
_compare = compare;
2727

28-
SortedList.from(SortedList<E> list)
29-
: _items = List.from(list._items),
30-
_compare = list._compare;
31-
3228
// Required for ListMixin
3329
@override
3430
set length(int newLength) {
@@ -79,6 +75,14 @@ class SortedList<E> with ListMixin<E> {
7975
}
8076
}
8177

78+
/// Returns a copy of the object
79+
SortedList<E> copy() {
80+
return SortedList.fromPresortedList(
81+
items: List.from(_items),
82+
compare: _compare,
83+
);
84+
}
85+
8286
/// Finds the index to insert the [item] at
8387
///
8488
/// O(log(n)) time complexity

packages/amplify_datastore_plugin_interface/test/query_snapshot_test.dart

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,39 @@ void main() {
3939
expect(updatedSnapshot.items.length, snapshot.items.length + 1);
4040
expect(updatedSnapshot.items.last, newBlog);
4141
});
42+
43+
test(
44+
'returns a QuerySnapshot with an updated item if the item already exists',
45+
() async {
46+
final blog = Blog(name: 'new blog');
47+
48+
final subscriptionEvent = SubscriptionEvent(
49+
item: blog,
50+
modelType: Blog.classType,
51+
eventType: EventType.create,
52+
);
53+
54+
final updatedSnapshot = snapshot.withSubscriptionEvent(
55+
event: subscriptionEvent,
56+
);
57+
58+
expect(updatedSnapshot.items.contains(blog), isTrue);
59+
60+
final updatedBlog = blog.copyWith(name: 'updated name');
61+
62+
final subscriptionEvent2 = SubscriptionEvent(
63+
item: updatedBlog,
64+
modelType: Blog.classType,
65+
eventType: EventType.create,
66+
);
67+
68+
final updatedSnapshot2 = updatedSnapshot.withSubscriptionEvent(
69+
event: subscriptionEvent2,
70+
);
71+
72+
expect(updatedSnapshot2.items.contains(updatedBlog), isTrue);
73+
expect(updatedSnapshot2.items.contains(blog), isFalse);
74+
});
4275
});
4376

4477
group('update event', () {

packages/amplify_datastore_plugin_interface/test/sorted_list_test.dart

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
import 'package:amplify_datastore_plugin_interface/amplify_datastore_plugin_interface.dart';
5+
import 'package:collection/collection.dart';
56
import 'package:flutter_test/flutter_test.dart';
67

78
void main() {
@@ -61,5 +62,20 @@ void main() {
6162
expect(sortedList.toList(), orderedEquals(expectedItems));
6263
});
6364
});
65+
66+
group('copy()', () {
67+
test('returns a copy of the current list', () {
68+
final items = [1, 2, 3, 7, 10];
69+
final sortedList = SortedList.fromPresortedList(items: items);
70+
final copy = sortedList.copy();
71+
72+
expect(sortedList.equals(copy), isTrue);
73+
74+
copy.add(20);
75+
expect(sortedList.equals(copy), isFalse);
76+
expect(sortedList.length, items.length);
77+
expect(copy.length, items.length + 1);
78+
});
79+
});
6480
});
6581
}

0 commit comments

Comments
 (0)