@@ -83,6 +83,24 @@ class ParseLiveList<T extends ParseObject> {
83
83
return _list.length;
84
84
}
85
85
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
+
86
104
Stream <ParseLiveListEvent <T >> get stream => _eventStreamController.stream;
87
105
Subscription <T > _liveQuerySubscription;
88
106
StreamSubscription <LiveQueryClientEvent > _liveQueryClientEventSubscription;
@@ -92,9 +110,9 @@ class ParseLiveList<T extends ParseObject> {
92
110
if (query.limiters.containsKey ('order' )) {
93
111
query.keysToReturn (
94
112
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
+ }
98
116
return string;
99
117
}).toList ());
100
118
} else {
@@ -180,7 +198,92 @@ class ParseLiveList<T extends ParseObject> {
180
198
});
181
199
}
182
200
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);
184
287
for (int i = 0 ; i < _list.length; i++ ) {
185
288
if (after (object, _list[i].object) != true ) {
186
289
_list.insert (i, ParseLiveListElement <T >(object, loaded: loaded));
@@ -194,20 +297,19 @@ class ParseLiveList<T extends ParseObject> {
194
297
_list.length - 1 , object? .clone (object? .toJson (full: true ))));
195
298
}
196
299
197
- void _objectUpdated (T object) {
300
+ Future < void > _objectUpdated (T object) async {
198
301
for (int i = 0 ; i < _list.length; i++ ) {
199
302
if (_list[i].object.get <String >(keyVarObjectId) ==
200
303
object.get <String >(keyVarObjectId)) {
304
+ await _loadIncludes (object, oldObject: _list[i].object);
201
305
if (after (_list[i].object, object) == null ) {
202
- _list[i].object = object;
306
+ _list[i].object = object? . clone (object ? . toJson (full : true )) ;
203
307
} else {
204
308
_list.removeAt (i).dispose ();
205
309
_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 );
211
313
}
212
314
break ;
213
315
}
@@ -255,6 +357,15 @@ class ParseLiveList<T extends ParseObject> {
255
357
return 'NotFound' ;
256
358
}
257
359
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
+
258
369
T getLoadedAt (int index) {
259
370
if (index < _list.length && _list[index].loaded) {
260
371
return _list[index].object;
@@ -308,7 +419,7 @@ class ParseLiveListElement<T extends ParseObject> {
308
419
}
309
420
310
421
abstract class ParseLiveListEvent <T extends ParseObject > {
311
- ParseLiveListEvent (this ._index, this ._object); //, this._object);
422
+ ParseLiveListEvent (this ._index, this ._object);
312
423
313
424
final int _index;
314
425
final T _object;
@@ -459,7 +570,8 @@ class _ParseLiveListWidgetState<T extends ParseObject>
459
570
itemBuilder:
460
571
(BuildContext context, int index, Animation <double > animation) {
461
572
return ParseLiveListElementWidget <T >(
462
- key: ValueKey <String >(_liveList? .idOf (index) ?? '_NotFound' ),
573
+ key: ValueKey <String >(
574
+ _liveList? .getIdentifier (index) ?? '_NotFound' ),
463
575
stream: () => _liveList? .getAt (index),
464
576
loadedData: () => _liveList? .getLoadedAt (index),
465
577
sizeFactor: animation,
@@ -505,7 +617,6 @@ class _ParseLiveListElementWidgetState<T extends ParseObject>
505
617
with SingleTickerProviderStateMixin {
506
618
_ParseLiveListElementWidgetState (
507
619
DataGetter <T > loadedDataGetter, StreamGetter <T > stream) {
508
- // loadedData = loadedDataGetter();
509
620
_snapshot = ParseLiveListElementSnapshot <T >(loadedData: loadedDataGetter ());
510
621
if (stream != null ) {
511
622
_streamSubscription = stream ().listen (
0 commit comments