From dd837d190ad4b880ca9317eea480d0f1ba39a917 Mon Sep 17 00:00:00 2001 From: maaeps Date: Thu, 2 Apr 2020 12:45:41 +0200 Subject: [PATCH 1/5] LiveList: reload included objects First implementation --- lib/src/utils/parse_live_list.dart | 74 +++++++++++++++++++++++++++--- 1 file changed, 68 insertions(+), 6 deletions(-) diff --git a/lib/src/utils/parse_live_list.dart b/lib/src/utils/parse_live_list.dart index 6c063ba6a..6176ade57 100644 --- a/lib/src/utils/parse_live_list.dart +++ b/lib/src/utils/parse_live_list.dart @@ -83,6 +83,30 @@ class ParseLiveList { return _list.length; } + List get includes => + _query.limiters['include']?.toString()?.split(',') ?? []; + + List> get validIncludePaths { + final List> includePaths = includes + .map>((String value) => value?.split('.')) + .toList(); + includePaths + .sort((List a, List b) => a.length.compareTo(b.length)); + return includePaths + // ignore: unnecessary_parenthesis + .where((List element) => (element.length == 1 || + includePaths.any((List otherElement) { + if (element.length - 1 == otherElement.length) { + for (int i = 0; i < element.length - 1; i++) { + if (element[i] != otherElement[i]) return false; + } + return true; + } + return false; + }))) + .toList(); + } + Stream> get stream => _eventStreamController.stream; Subscription _liveQuerySubscription; StreamSubscription _liveQueryClientEventSubscription; @@ -92,9 +116,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 +204,43 @@ class ParseLiveList { }); } - void _objectAdded(T object, {bool loaded = true}) { + // TODO(any): include sub-includes & use already available data. + Future _loadIncludes(T object, + {T oldObject}) async { + if (object == null) return null; + object = object.clone(object.toJson(full: true)); + final List> includes = validIncludePaths; + final List TLIncludes = includes + .where((element) => element.length == 1) + .map((e) => e[0]) + .toList(); + final Map>> loadedObjects = + >>{}; + for (String key in TLIncludes) { + if (object.containsKey(key)) { + final QueryBuilder queryBuilder = + QueryBuilder(object.get(key)); + queryBuilder.whereEqualTo( + keyVarObjectId, object.get(key).objectId); + + loadedObjects.putIfAbsent( + key, + () => queryBuilder.query().then((ParseResponse value) => + MapEntry(key, value))); + } + } + final List> responses = + await Future.wait>( + loadedObjects.values); + for (MapEntry entry in responses) { + if (entry.value.success && entry.value.results.length == 1) + object.getObjectData()[entry.key] = entry.value.results[0]; + } + return object; + } + + Future _objectAdded(T object, {bool loaded = true}) async { + object = 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,7 +254,9 @@ class ParseLiveList { _list.length - 1, object?.clone(object?.toJson(full: true)))); } - void _objectUpdated(T object) { + Future _objectUpdated(T object) async { + print(object); + object = await _loadIncludes(object); for (int i = 0; i < _list.length; i++) { if (_list[i].object.get(keyVarObjectId) == object.get(keyVarObjectId)) { @@ -203,7 +265,7 @@ class ParseLiveList { } else { _list.removeAt(i).dispose(); _eventStreamController.sink.add(ParseLiveListDeleteEvent( - // ignore: invalid_use_of_protected_member + // ignore: invalid_use_of_protected_member i, object?.clone(object?.toJson(full: true)))); // ignore: invalid_use_of_protected_member From a30de875128e03496996ea05ef83291fe4d5f802 Mon Sep 17 00:00:00 2001 From: maaeps Date: Thu, 2 Apr 2020 13:05:46 +0200 Subject: [PATCH 2/5] Key was wrong on order change --- lib/src/utils/parse_live_list.dart | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/src/utils/parse_live_list.dart b/lib/src/utils/parse_live_list.dart index 6176ade57..39e27cb1c 100644 --- a/lib/src/utils/parse_live_list.dart +++ b/lib/src/utils/parse_live_list.dart @@ -317,6 +317,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; @@ -521,7 +530,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, From 8d4a4aff902401a044219a72438f051ec838aa3b Mon Sep 17 00:00:00 2001 From: maaeps Date: Thu, 2 Apr 2020 14:41:41 +0200 Subject: [PATCH 3/5] include sub-includes --- lib/src/utils/parse_live_list.dart | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/src/utils/parse_live_list.dart b/lib/src/utils/parse_live_list.dart index 39e27cb1c..3bb17902c 100644 --- a/lib/src/utils/parse_live_list.dart +++ b/lib/src/utils/parse_live_list.dart @@ -204,7 +204,7 @@ class ParseLiveList { }); } - // TODO(any): include sub-includes & use already available data. + // TODO(any): use already available data. Future _loadIncludes(T object, {T oldObject}) async { if (object == null) return null; @@ -222,7 +222,18 @@ class ParseLiveList { QueryBuilder(object.get(key)); queryBuilder.whereEqualTo( keyVarObjectId, object.get(key).objectId); - + queryBuilder.includeObject(includes + .where((List path) => path.length > 1 && path[0] == key) + .map((List path) { + String val = ''; + for (int i = 1; i < path.length; i++) { + if (i > 1) { + val += '.'; + } + val += path[i]; + } + return val; + }).toList()); loadedObjects.putIfAbsent( key, () => queryBuilder.query().then((ParseResponse value) => @@ -265,10 +276,7 @@ class ParseLiveList { } 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 + i, object?.clone(object?.toJson(full: true)))); _objectAdded(object?.clone(object?.toJson(full: true))); } break; From f4860d22dfbc2430dcf29cd47b8564c8823f4e7a Mon Sep 17 00:00:00 2001 From: maaeps Date: Sat, 4 Apr 2020 13:07:16 +0200 Subject: [PATCH 4/5] reuse loaded pointers --- lib/src/utils/parse_live_list.dart | 224 +++++++++++++++++++++-------- 1 file changed, 162 insertions(+), 62 deletions(-) diff --git a/lib/src/utils/parse_live_list.dart b/lib/src/utils/parse_live_list.dart index 3bb17902c..c9c3506d0 100644 --- a/lib/src/utils/parse_live_list.dart +++ b/lib/src/utils/parse_live_list.dart @@ -86,25 +86,40 @@ class ParseLiveList { List get includes => _query.limiters['include']?.toString()?.split(',') ?? []; - List> get validIncludePaths { - final List> includePaths = includes - .map>((String value) => value?.split('.')) - .toList(); - includePaths - .sort((List a, List b) => a.length.compareTo(b.length)); - return includePaths - // ignore: unnecessary_parenthesis - .where((List element) => (element.length == 1 || - includePaths.any((List otherElement) { - if (element.length - 1 == otherElement.length) { - for (int i = 0; i < element.length - 1; i++) { - if (element[i] != otherElement[i]) return false; - } - return true; - } - return false; - }))) - .toList(); +// List> get validIncludePaths { +// final List> includePaths = includes +// .map>((String value) => value?.split('.')) +// .toList(); +// includePaths +// .sort((List a, List b) => a.length.compareTo(b.length)); +// return includePaths +// // ignore: unnecessary_parenthesis +// .where((List element) => (element.length == 1 || +// includePaths.any((List otherElement) { +// if (element.length - 1 == otherElement.length) { +// for (int i = 0; i < element.length - 1; i++) { +// if (element[i] != otherElement[i]) return false; +// } +// return true; +// } +// return false; +// }))) +// .toList(); +// } + + 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; @@ -204,54 +219,138 @@ class ParseLiveList { }); } - // TODO(any): use already available data. - Future _loadIncludes(T object, - {T oldObject}) async { - if (object == null) return null; - object = object.clone(object.toJson(full: true)); - final List> includes = validIncludePaths; - final List TLIncludes = includes - .where((element) => element.length == 1) - .map((e) => e[0]) - .toList(); - final Map>> loadedObjects = - >>{}; - for (String key in TLIncludes) { + 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)) { - final QueryBuilder queryBuilder = - QueryBuilder(object.get(key)); - queryBuilder.whereEqualTo( - keyVarObjectId, object.get(key).objectId); - queryBuilder.includeObject(includes - .where((List path) => path.length > 1 && path[0] == key) - .map((List path) { - String val = ''; - for (int i = 1; i < path.length; i++) { - if (i > 1) { - val += '.'; + 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) { + print('fetch1 $key'); + //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) { + print('fetched1 $key'); + if (parseResponse.success && + parseResponse.results.length == 1) { + object.getObjectData()[key] = parseResponse.results[0]; + } + })); + continue; + } else { + print('recycled $key'); + object.getObjectData()[key] = includedObject; + //recursion + loadingNodes + .add(_loadIncludes(includedObject, paths: paths[key])); + continue; } - val += path[i]; + } else { + print('fetch2 $key'); + //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) { + print('fetched2 $key'); + if (parseResponse.success && parseResponse.results.length == 1) { + object.getObjectData()[key] = parseResponse.results[0]; + } + })); + continue; } - return val; - }).toList()); - loadedObjects.putIfAbsent( - key, - () => queryBuilder.query().then((ParseResponse value) => - MapEntry(key, value))); + } else { + print('recycled $key'); + //recursion + loadingNodes.add(_loadIncludes(includedObject, + oldObject: oldObject?.get(key), paths: paths[key])); + continue; + } + } else { + //All fine for this key + continue; } } - final List> responses = - await Future.wait>( - loadedObjects.values); - for (MapEntry entry in responses) { - if (entry.value.success && entry.value.results.length == 1) - object.getObjectData()[entry.key] = entry.value.results[0]; + await Future.wait(loadingNodes); + +// final List> includes = validIncludePaths; +// final List TLIncludes = includes +// .where((element) => element.length == 1) +// .map((e) => e[0]) +// .toList(); +// final Map>> loadedObjects = +// >>{}; +// for (String key in TLIncludes) { +// if (object.containsKey(key)) { +// final QueryBuilder queryBuilder = +// QueryBuilder(object.get(key)); +// queryBuilder.whereEqualTo( +// keyVarObjectId, object.get(key).objectId); +// queryBuilder.includeObject(includes +// .where((List path) => path.length > 1 && path[0] == key) +// .map((List path) { +// String val = ''; +// for (int i = 1; i < path.length; i++) { +// if (i > 1) { +// val += '.'; +// } +// val += path[i]; +// } +// return val; +// }).toList()); +// loadedObjects.putIfAbsent( +// key, +// () => queryBuilder.query().then((ParseResponse value) => +// MapEntry(key, value))); +// } +// } +// final List> responses = +// await Future.wait>( +// loadedObjects.values); +// for (MapEntry entry in responses) { +// if (entry.value.success && entry.value.results.length == 1) +// object.getObjectData()[entry.key] = entry.value.results[0]; +// } +// return object; + } + + 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 object; + return includeList; } - Future _objectAdded(T object, {bool loaded = true}) async { - object = await _loadIncludes(object); + 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)); @@ -267,17 +366,18 @@ class ParseLiveList { Future _objectUpdated(T object) async { print(object); - object = await _loadIncludes(object); 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( i, object?.clone(object?.toJson(full: true)))); - _objectAdded(object?.clone(object?.toJson(full: true))); + _objectAdded(object?.clone(object?.toJson(full: true)), + fetchedIncludes: true); } break; } From 31a498b178a8be864ec27664f12fc09d1fab65e8 Mon Sep 17 00:00:00 2001 From: maaeps Date: Sat, 4 Apr 2020 13:09:52 +0200 Subject: [PATCH 5/5] Deleted stuff --- lib/src/utils/parse_live_list.dart | 71 +----------------------------- 1 file changed, 1 insertion(+), 70 deletions(-) diff --git a/lib/src/utils/parse_live_list.dart b/lib/src/utils/parse_live_list.dart index c9c3506d0..ecdd8663f 100644 --- a/lib/src/utils/parse_live_list.dart +++ b/lib/src/utils/parse_live_list.dart @@ -86,27 +86,6 @@ class ParseLiveList { List get includes => _query.limiters['include']?.toString()?.split(',') ?? []; -// List> get validIncludePaths { -// final List> includePaths = includes -// .map>((String value) => value?.split('.')) -// .toList(); -// includePaths -// .sort((List a, List b) => a.length.compareTo(b.length)); -// return includePaths -// // ignore: unnecessary_parenthesis -// .where((List element) => (element.length == 1 || -// includePaths.any((List otherElement) { -// if (element.length - 1 == otherElement.length) { -// for (int i = 0; i < element.length - 1; i++) { -// if (element[i] != otherElement[i]) return false; -// } -// return true; -// } -// return false; -// }))) -// .toList(); -// } - Map get _includePaths { final Map includesMap = {}; @@ -238,7 +217,6 @@ class ParseLiveList { if (!includedObject.containsKey(keyVarUpdatedAt) || includedObject.objectId != object.get(key).objectId) { - print('fetch1 $key'); //fetch from web including sub objects //same as down there final QueryBuilder queryBuilder = QueryBuilder< @@ -248,7 +226,6 @@ class ParseLiveList { loadingNodes.add(queryBuilder .query() .then((ParseResponse parseResponse) { - print('fetched1 $key'); if (parseResponse.success && parseResponse.results.length == 1) { object.getObjectData()[key] = parseResponse.results[0]; @@ -256,7 +233,6 @@ class ParseLiveList { })); continue; } else { - print('recycled $key'); object.getObjectData()[key] = includedObject; //recursion loadingNodes @@ -264,7 +240,6 @@ class ParseLiveList { continue; } } else { - print('fetch2 $key'); //fetch from web including sub objects //same as up there final QueryBuilder queryBuilder = QueryBuilder< @@ -273,7 +248,6 @@ class ParseLiveList { ..includeObject(_toIncludeStringList(paths[key])); loadingNodes.add( queryBuilder.query().then((ParseResponse parseResponse) { - print('fetched2 $key'); if (parseResponse.success && parseResponse.results.length == 1) { object.getObjectData()[key] = parseResponse.results[0]; } @@ -281,7 +255,6 @@ class ParseLiveList { continue; } } else { - print('recycled $key'); //recursion loadingNodes.add(_loadIncludes(includedObject, oldObject: oldObject?.get(key), paths: paths[key])); @@ -293,46 +266,6 @@ class ParseLiveList { } } await Future.wait(loadingNodes); - -// final List> includes = validIncludePaths; -// final List TLIncludes = includes -// .where((element) => element.length == 1) -// .map((e) => e[0]) -// .toList(); -// final Map>> loadedObjects = -// >>{}; -// for (String key in TLIncludes) { -// if (object.containsKey(key)) { -// final QueryBuilder queryBuilder = -// QueryBuilder(object.get(key)); -// queryBuilder.whereEqualTo( -// keyVarObjectId, object.get(key).objectId); -// queryBuilder.includeObject(includes -// .where((List path) => path.length > 1 && path[0] == key) -// .map((List path) { -// String val = ''; -// for (int i = 1; i < path.length; i++) { -// if (i > 1) { -// val += '.'; -// } -// val += path[i]; -// } -// return val; -// }).toList()); -// loadedObjects.putIfAbsent( -// key, -// () => queryBuilder.query().then((ParseResponse value) => -// MapEntry(key, value))); -// } -// } -// final List> responses = -// await Future.wait>( -// loadedObjects.values); -// for (MapEntry entry in responses) { -// if (entry.value.success && entry.value.results.length == 1) -// object.getObjectData()[entry.key] = entry.value.results[0]; -// } -// return object; } List _toIncludeStringList(Map includes) { @@ -365,7 +298,6 @@ class ParseLiveList { } Future _objectUpdated(T object) async { - print(object); for (int i = 0; i < _list.length; i++) { if (_list[i].object.get(keyVarObjectId) == object.get(keyVarObjectId)) { @@ -487,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; @@ -685,7 +617,6 @@ class _ParseLiveListElementWidgetState with SingleTickerProviderStateMixin { _ParseLiveListElementWidgetState( DataGetter loadedDataGetter, StreamGetter stream) { -// loadedData = loadedDataGetter(); _snapshot = ParseLiveListElementSnapshot(loadedData: loadedDataGetter()); if (stream != null) { _streamSubscription = stream().listen(