diff --git a/lib/src/utils/parse_live_list.dart b/lib/src/utils/parse_live_list.dart index 6c063ba6a..ecdd8663f 100644 --- a/lib/src/utils/parse_live_list.dart +++ b/lib/src/utils/parse_live_list.dart @@ -83,6 +83,24 @@ class ParseLiveList { return _list.length; } + List get includes => + _query.limiters['include']?.toString()?.split(',') ?? []; + + Map get _includePaths { + final Map includesMap = {}; + + for (String includeString in includes) { + final List pathParts = includeString.split('.'); + Map root = includesMap; + for (String pathPart in pathParts) { + root.putIfAbsent(pathPart, () => {}); + root = root[pathPart]; + } + } + + return includesMap; + } + Stream> get stream => _eventStreamController.stream; Subscription _liveQuerySubscription; StreamSubscription _liveQueryClientEventSubscription; @@ -92,9 +110,9 @@ class ParseLiveList { if (query.limiters.containsKey('order')) { query.keysToReturn( query.limiters['order'].toString().split(',').map((String string) { - if (string.startsWith('-')) { - return string.substring(1); - } + if (string.startsWith('-')) { + return string.substring(1); + } return string; }).toList()); } else { @@ -180,7 +198,92 @@ class ParseLiveList { }); } - void _objectAdded(T object, {bool loaded = true}) { + Future _loadIncludes(ParseObject object, + {ParseObject oldObject, Map paths}) async { + paths ??= _includePaths; + if (object == null || paths.isEmpty) return; + + final List> loadingNodes = >[]; + + for (String key in paths.keys) { + if (object.containsKey(key)) { + ParseObject includedObject = object.get(key); + //If the object is not fetched + if (!includedObject.containsKey(keyVarUpdatedAt)) { + //See if oldObject contains key + if (oldObject != null && oldObject.containsKey(key)) { + includedObject = oldObject.get(key); + //If the object is not fetched || the ids don't match / the pointer changed + if (!includedObject.containsKey(keyVarUpdatedAt) || + includedObject.objectId != + object.get(key).objectId) { + //fetch from web including sub objects + //same as down there + final QueryBuilder queryBuilder = QueryBuilder< + ParseObject>(ParseObject(includedObject.parseClassName)) + ..whereEqualTo(keyVarObjectId, includedObject.objectId) + ..includeObject(_toIncludeStringList(paths[key])); + loadingNodes.add(queryBuilder + .query() + .then((ParseResponse parseResponse) { + if (parseResponse.success && + parseResponse.results.length == 1) { + object.getObjectData()[key] = parseResponse.results[0]; + } + })); + continue; + } else { + object.getObjectData()[key] = includedObject; + //recursion + loadingNodes + .add(_loadIncludes(includedObject, paths: paths[key])); + continue; + } + } else { + //fetch from web including sub objects + //same as up there + final QueryBuilder queryBuilder = QueryBuilder< + ParseObject>(ParseObject(includedObject.parseClassName)) + ..whereEqualTo(keyVarObjectId, includedObject.objectId) + ..includeObject(_toIncludeStringList(paths[key])); + loadingNodes.add( + queryBuilder.query().then((ParseResponse parseResponse) { + if (parseResponse.success && parseResponse.results.length == 1) { + object.getObjectData()[key] = parseResponse.results[0]; + } + })); + continue; + } + } else { + //recursion + loadingNodes.add(_loadIncludes(includedObject, + oldObject: oldObject?.get(key), paths: paths[key])); + continue; + } + } else { + //All fine for this key + continue; + } + } + await Future.wait(loadingNodes); + } + + List _toIncludeStringList(Map includes) { + final List includeList = []; + for (String key in includes.keys) { + includeList.add(key); + // ignore: avoid_as + if ((includes[key] as Map).isNotEmpty) { + includeList + .addAll(_toIncludeStringList(includes[key]).map((e) => '$key.$e')); + } + } + return includeList; + } + + Future _objectAdded(T object, + {bool loaded = true, bool fetchedIncludes = false}) async { + if (!fetchedIncludes) await _loadIncludes(object); for (int i = 0; i < _list.length; i++) { if (after(object, _list[i].object) != true) { _list.insert(i, ParseLiveListElement(object, loaded: loaded)); @@ -194,20 +297,19 @@ class ParseLiveList { _list.length - 1, object?.clone(object?.toJson(full: true)))); } - void _objectUpdated(T object) { + Future _objectUpdated(T object) async { for (int i = 0; i < _list.length; i++) { if (_list[i].object.get(keyVarObjectId) == object.get(keyVarObjectId)) { + await _loadIncludes(object, oldObject: _list[i].object); if (after(_list[i].object, object) == null) { - _list[i].object = object; + _list[i].object = object?.clone(object?.toJson(full: true)); } else { _list.removeAt(i).dispose(); _eventStreamController.sink.add(ParseLiveListDeleteEvent( - // ignore: invalid_use_of_protected_member - i, - object?.clone(object?.toJson(full: true)))); - // ignore: invalid_use_of_protected_member - _objectAdded(object?.clone(object?.toJson(full: true))); + i, object?.clone(object?.toJson(full: true)))); + _objectAdded(object?.clone(object?.toJson(full: true)), + fetchedIncludes: true); } break; } @@ -255,6 +357,15 @@ class ParseLiveList { return 'NotFound'; } + String getIdentifier(int index) { + if (index < _list.length) { + return _list[index].object.get(keyVarObjectId) + + _list[index].object.get(keyVarUpdatedAt)?.toString() ?? + ''; + } + return 'NotFound'; + } + T getLoadedAt(int index) { if (index < _list.length && _list[index].loaded) { return _list[index].object; @@ -308,7 +419,7 @@ class ParseLiveListElement { } abstract class ParseLiveListEvent { - ParseLiveListEvent(this._index, this._object); //, this._object); + ParseLiveListEvent(this._index, this._object); final int _index; final T _object; @@ -459,7 +570,8 @@ class _ParseLiveListWidgetState itemBuilder: (BuildContext context, int index, Animation animation) { return ParseLiveListElementWidget( - key: ValueKey(_liveList?.idOf(index) ?? '_NotFound'), + key: ValueKey( + _liveList?.getIdentifier(index) ?? '_NotFound'), stream: () => _liveList?.getAt(index), loadedData: () => _liveList?.getLoadedAt(index), sizeFactor: animation, @@ -505,7 +617,6 @@ class _ParseLiveListElementWidgetState with SingleTickerProviderStateMixin { _ParseLiveListElementWidgetState( DataGetter loadedDataGetter, StreamGetter stream) { -// loadedData = loadedDataGetter(); _snapshot = ParseLiveListElementSnapshot(loadedData: loadedDataGetter()); if (stream != null) { _streamSubscription = stream().listen(