Skip to content

Commit f095e6d

Browse files
authored
Fix for #347 (include in LiveList) (#348)
* LiveList: reload included objects First implementation * Key was wrong on order change * include sub-includes * reuse loaded pointers * Deleted stuff
1 parent ed4d390 commit f095e6d

File tree

1 file changed

+125
-14
lines changed

1 file changed

+125
-14
lines changed

lib/src/utils/parse_live_list.dart

Lines changed: 125 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,24 @@ class ParseLiveList<T extends ParseObject> {
8383
return _list.length;
8484
}
8585

86+
List<String> get includes =>
87+
_query.limiters['include']?.toString()?.split(',') ?? <String>[];
88+
89+
Map<String, dynamic> get _includePaths {
90+
final Map<String, dynamic> includesMap = <String, dynamic>{};
91+
92+
for (String includeString in includes) {
93+
final List<String> pathParts = includeString.split('.');
94+
Map<String, dynamic> root = includesMap;
95+
for (String pathPart in pathParts) {
96+
root.putIfAbsent(pathPart, () => <String, dynamic>{});
97+
root = root[pathPart];
98+
}
99+
}
100+
101+
return includesMap;
102+
}
103+
86104
Stream<ParseLiveListEvent<T>> get stream => _eventStreamController.stream;
87105
Subscription<T> _liveQuerySubscription;
88106
StreamSubscription<LiveQueryClientEvent> _liveQueryClientEventSubscription;
@@ -92,9 +110,9 @@ class ParseLiveList<T extends ParseObject> {
92110
if (query.limiters.containsKey('order')) {
93111
query.keysToReturn(
94112
query.limiters['order'].toString().split(',').map((String string) {
95-
if (string.startsWith('-')) {
96-
return string.substring(1);
97-
}
113+
if (string.startsWith('-')) {
114+
return string.substring(1);
115+
}
98116
return string;
99117
}).toList());
100118
} else {
@@ -180,7 +198,92 @@ class ParseLiveList<T extends ParseObject> {
180198
});
181199
}
182200

183-
void _objectAdded(T object, {bool loaded = true}) {
201+
Future<void> _loadIncludes(ParseObject object,
202+
{ParseObject oldObject, Map<String, dynamic> paths}) async {
203+
paths ??= _includePaths;
204+
if (object == null || paths.isEmpty) return;
205+
206+
final List<Future<void>> loadingNodes = <Future<void>>[];
207+
208+
for (String key in paths.keys) {
209+
if (object.containsKey(key)) {
210+
ParseObject includedObject = object.get<ParseObject>(key);
211+
//If the object is not fetched
212+
if (!includedObject.containsKey(keyVarUpdatedAt)) {
213+
//See if oldObject contains key
214+
if (oldObject != null && oldObject.containsKey(key)) {
215+
includedObject = oldObject.get<ParseObject>(key);
216+
//If the object is not fetched || the ids don't match / the pointer changed
217+
if (!includedObject.containsKey(keyVarUpdatedAt) ||
218+
includedObject.objectId !=
219+
object.get<ParseObject>(key).objectId) {
220+
//fetch from web including sub objects
221+
//same as down there
222+
final QueryBuilder<ParseObject> queryBuilder = QueryBuilder<
223+
ParseObject>(ParseObject(includedObject.parseClassName))
224+
..whereEqualTo(keyVarObjectId, includedObject.objectId)
225+
..includeObject(_toIncludeStringList(paths[key]));
226+
loadingNodes.add(queryBuilder
227+
.query()
228+
.then<void>((ParseResponse parseResponse) {
229+
if (parseResponse.success &&
230+
parseResponse.results.length == 1) {
231+
object.getObjectData()[key] = parseResponse.results[0];
232+
}
233+
}));
234+
continue;
235+
} else {
236+
object.getObjectData()[key] = includedObject;
237+
//recursion
238+
loadingNodes
239+
.add(_loadIncludes(includedObject, paths: paths[key]));
240+
continue;
241+
}
242+
} else {
243+
//fetch from web including sub objects
244+
//same as up there
245+
final QueryBuilder<ParseObject> queryBuilder = QueryBuilder<
246+
ParseObject>(ParseObject(includedObject.parseClassName))
247+
..whereEqualTo(keyVarObjectId, includedObject.objectId)
248+
..includeObject(_toIncludeStringList(paths[key]));
249+
loadingNodes.add(
250+
queryBuilder.query().then<void>((ParseResponse parseResponse) {
251+
if (parseResponse.success && parseResponse.results.length == 1) {
252+
object.getObjectData()[key] = parseResponse.results[0];
253+
}
254+
}));
255+
continue;
256+
}
257+
} else {
258+
//recursion
259+
loadingNodes.add(_loadIncludes(includedObject,
260+
oldObject: oldObject?.get(key), paths: paths[key]));
261+
continue;
262+
}
263+
} else {
264+
//All fine for this key
265+
continue;
266+
}
267+
}
268+
await Future.wait(loadingNodes);
269+
}
270+
271+
List<String> _toIncludeStringList(Map<String, dynamic> includes) {
272+
final List<String> includeList = <String>[];
273+
for (String key in includes.keys) {
274+
includeList.add(key);
275+
// ignore: avoid_as
276+
if ((includes[key] as Map<String, dynamic>).isNotEmpty) {
277+
includeList
278+
.addAll(_toIncludeStringList(includes[key]).map((e) => '$key.$e'));
279+
}
280+
}
281+
return includeList;
282+
}
283+
284+
Future<void> _objectAdded(T object,
285+
{bool loaded = true, bool fetchedIncludes = false}) async {
286+
if (!fetchedIncludes) await _loadIncludes(object);
184287
for (int i = 0; i < _list.length; i++) {
185288
if (after(object, _list[i].object) != true) {
186289
_list.insert(i, ParseLiveListElement<T>(object, loaded: loaded));
@@ -194,20 +297,19 @@ class ParseLiveList<T extends ParseObject> {
194297
_list.length - 1, object?.clone(object?.toJson(full: true))));
195298
}
196299

197-
void _objectUpdated(T object) {
300+
Future<void> _objectUpdated(T object) async {
198301
for (int i = 0; i < _list.length; i++) {
199302
if (_list[i].object.get<String>(keyVarObjectId) ==
200303
object.get<String>(keyVarObjectId)) {
304+
await _loadIncludes(object, oldObject: _list[i].object);
201305
if (after(_list[i].object, object) == null) {
202-
_list[i].object = object;
306+
_list[i].object = object?.clone(object?.toJson(full: true));
203307
} else {
204308
_list.removeAt(i).dispose();
205309
_eventStreamController.sink.add(ParseLiveListDeleteEvent<T>(
206-
// ignore: invalid_use_of_protected_member
207-
i,
208-
object?.clone(object?.toJson(full: true))));
209-
// ignore: invalid_use_of_protected_member
210-
_objectAdded(object?.clone(object?.toJson(full: true)));
310+
i, object?.clone(object?.toJson(full: true))));
311+
_objectAdded(object?.clone(object?.toJson(full: true)),
312+
fetchedIncludes: true);
211313
}
212314
break;
213315
}
@@ -255,6 +357,15 @@ class ParseLiveList<T extends ParseObject> {
255357
return 'NotFound';
256358
}
257359

360+
String getIdentifier(int index) {
361+
if (index < _list.length) {
362+
return _list[index].object.get<String>(keyVarObjectId) +
363+
_list[index].object.get<DateTime>(keyVarUpdatedAt)?.toString() ??
364+
'';
365+
}
366+
return 'NotFound';
367+
}
368+
258369
T getLoadedAt(int index) {
259370
if (index < _list.length && _list[index].loaded) {
260371
return _list[index].object;
@@ -308,7 +419,7 @@ class ParseLiveListElement<T extends ParseObject> {
308419
}
309420

310421
abstract class ParseLiveListEvent<T extends ParseObject> {
311-
ParseLiveListEvent(this._index, this._object); //, this._object);
422+
ParseLiveListEvent(this._index, this._object);
312423

313424
final int _index;
314425
final T _object;
@@ -459,7 +570,8 @@ class _ParseLiveListWidgetState<T extends ParseObject>
459570
itemBuilder:
460571
(BuildContext context, int index, Animation<double> animation) {
461572
return ParseLiveListElementWidget<T>(
462-
key: ValueKey<String>(_liveList?.idOf(index) ?? '_NotFound'),
573+
key: ValueKey<String>(
574+
_liveList?.getIdentifier(index) ?? '_NotFound'),
463575
stream: () => _liveList?.getAt(index),
464576
loadedData: () => _liveList?.getLoadedAt(index),
465577
sizeFactor: animation,
@@ -505,7 +617,6 @@ class _ParseLiveListElementWidgetState<T extends ParseObject>
505617
with SingleTickerProviderStateMixin {
506618
_ParseLiveListElementWidgetState(
507619
DataGetter<T> loadedDataGetter, StreamGetter<T> stream) {
508-
// loadedData = loadedDataGetter();
509620
_snapshot = ParseLiveListElementSnapshot<T>(loadedData: loadedDataGetter());
510621
if (stream != null) {
511622
_streamSubscription = stream().listen(

0 commit comments

Comments
 (0)