diff --git a/README.md b/README.md index 75efba3fd..9a71f8362 100644 --- a/README.md +++ b/README.md @@ -497,8 +497,6 @@ To activate listening for updates on all included objects, add `listenOnAllSubIt If you want ParseLiveList to listen for updates on only some sub-objects, use `listeningIncludes: const [/*all the included sub-objects*/]` instead. Just as QueryBuilder, ParseLiveList supports nested sub-objects too. -**NOTE:** Currently ParseLiveList wont update your sub-objects after your client reconnects to the web. - **NOTE:** To use this features you have to enable [Live Queries](#live-queries) first. ## Users diff --git a/lib/src/utils/parse_live_list.dart b/lib/src/utils/parse_live_list.dart index a5605f354..115935ae3 100644 --- a/lib/src/utils/parse_live_list.dart +++ b/lib/src/utils/parse_live_list.dart @@ -186,48 +186,59 @@ class ParseLiveList { .getClientEventStream .listen((LiveQueryClientEvent event) async { if (event == LiveQueryClientEvent.CONNECTED) { - final ParseResponse parseResponse = await _runQuery(); - if (parseResponse.success) { - final List newList = parseResponse.results ?? List(); - - //update List - for (int i = 0; i < _list.length; i++) { - final ParseObject currentObject = _list[i].object; - final String currentObjectId = - currentObject.get(keyVarObjectId); - - bool stillInList = false; - - for (int j = 0; j < newList.length; j++) { - if (newList[j].get(keyVarObjectId) == currentObjectId) { - stillInList = true; - if (newList[j] - .get(keyVarUpdatedAt) - .isAfter(currentObject.get(keyVarUpdatedAt))) { - final QueryBuilder queryBuilder = - QueryBuilder.copy(_query) - ..whereEqualTo(keyVarObjectId, currentObjectId); - queryBuilder.query().then((ParseResponse result) { - if (result.success && result.results != null) { - _objectUpdated(result.results.first); - } - }); + _updateQueue.whenComplete(() async { + List> tasks = >[]; + final ParseResponse parseResponse = await _runQuery(); + if (parseResponse.success) { + final List newList = parseResponse.results ?? List(); + + //update List + for (int i = 0; i < _list.length; i++) { + final ParseObject currentObject = _list[i].object; + final String currentObjectId = + currentObject.get(keyVarObjectId); + + bool stillInList = false; + + for (int j = 0; j < newList.length; j++) { + if (newList[j].get(keyVarObjectId) == currentObjectId) { + stillInList = true; + if (newList[j] + .get(keyVarUpdatedAt) + .isAfter(currentObject.get(keyVarUpdatedAt))) { + final QueryBuilder queryBuilder = + QueryBuilder.copy(_query) + ..whereEqualTo(keyVarObjectId, currentObjectId); + tasks.add(queryBuilder + .query() + .then((ParseResponse result) async { + if (result.success && result.results != null) { + await _objectUpdated(result.results.first); + } + })); + } + newList.removeAt(j); + j--; + break; } - newList.removeAt(j); - j--; - break; + } + if (!stillInList) { + _objectDeleted(currentObject); + i--; } } - if (!stillInList) { - _objectDeleted(currentObject); - i--; + + for (int i = 0; i < newList.length; i++) { + tasks.add(_objectAdded(newList[i], loaded: false)); } } - - for (int i = 0; i < newList.length; i++) { - _objectAdded(newList[i], loaded: false); + await Future.wait(tasks); + tasks = >[]; + for (ParseLiveListElement element in _list) { + tasks.add(element.reconnected()); } - } + await Future.wait(tasks); + }); } }); } @@ -351,7 +362,7 @@ class ParseLiveList { _list.removeAt(i).dispose(); _eventStreamController.sink.add(ParseLiveListDeleteEvent( i, object?.clone(object?.toJson(full: true)))); - _objectAdded(object?.clone(object?.toJson(full: true)), + await _objectAdded(object?.clone(object?.toJson(full: true)), fetchedIncludes: true); } break; @@ -450,7 +461,7 @@ class ParseLiveListElement { final StreamController _streamController = StreamController.broadcast(); T _object; bool _loaded = false; - Map>, dynamic> _updatedSubItems; + Map _updatedSubItems; LiveQuery _liveQuery; final Future _subscriptionQueue = Future.value(); @@ -459,21 +470,17 @@ class ParseLiveListElement { // ignore: invalid_use_of_protected_member T get object => _object?.clone(_object?.toJson(full: true)); - Map>, dynamic> _toSubscriptionMap( - Map map) { - Map>, dynamic> result = - Map>, dynamic>(); + Map _toSubscriptionMap(Map map) { + final Map result = Map(); for (String key in map.keys) { - result.putIfAbsent(MapEntry>(key, null), - () => _toSubscriptionMap(map[key])); + result.putIfAbsent(PathKey(key), () => _toSubscriptionMap(map[key])); } return result; } - Map _toKeyMap( - Map>, dynamic> map) { + Map _toKeyMap(Map map) { final Map result = Map(); - for (MapEntry> key in map.keys) { + for (PathKey key in map.keys) { result.putIfAbsent(key.key, () => _toKeyMap(map[key])); } return result; @@ -483,8 +490,7 @@ class ParseLiveListElement { _subscriptionQueue.whenComplete(() async { if (_updatedSubItems.isNotEmpty && _object != null) { final List> tasks = >[]; - for (MapEntry> key - in _updatedSubItems.keys) { + for (PathKey key in _updatedSubItems.keys) { tasks.add(_subscribeSubItem(_object, key, _object.get(key.key), _updatedSubItems[key])); } @@ -493,24 +499,21 @@ class ParseLiveListElement { }); } - void _unsubscribe( - Map>, dynamic> subscriptions) { - for (MapEntry> key - in subscriptions.keys) { - if (_liveQuery != null && key.value != null) - _liveQuery.client.unSubscribe(key.value); + void _unsubscribe(Map subscriptions) { + for (PathKey key in subscriptions.keys) { + if (_liveQuery != null && key.subscription != null) { + _liveQuery.client.unSubscribe(key.subscription); + key.subscription = null; + } _unsubscribe(subscriptions[key]); } } - Future _subscribeSubItem( - ParseObject parentObject, - MapEntry> currentKey, - ParseObject subObject, - Map>, dynamic> path) async { + Future _subscribeSubItem(ParseObject parentObject, PathKey currentKey, + ParseObject subObject, Map path) async { if (_liveQuery != null && subObject != null) { final List> tasks = >[]; - for (MapEntry> key in path.keys) { + for (PathKey key in path.keys) { tasks.add(_subscribeSubItem( subObject, key, subObject.get(key.key), path[key])); } @@ -521,6 +524,7 @@ class ParseLiveListElement { tasks.add(_liveQuery.client .subscribe(queryBuilder) .then((Subscription subscription) { + currentKey.subscription = subscription; subscription.on(LiveQueryEvent.update, (ParseObject newObject) async { _subscriptionQueue.whenComplete(() async { await ParseLiveList._loadIncludes(newObject, @@ -530,11 +534,11 @@ class ParseLiveListElement { _streamController ?.add(_object?.clone(_object?.toJson(full: true))); //Resubscribe subitems + // TODO(any): only resubscribe on changed pointers _unsubscribe(path); - for (MapEntry> key - in path.keys) { - tasks.add(_subscribeSubItem(subObject, key, - subObject.get(key.key), path[key])); + for (PathKey key in path.keys) { + tasks.add(_subscribeSubItem(newObject, key, + newObject.get(key.key), path[key])); } } await Future.wait(tasks); @@ -560,6 +564,70 @@ class ParseLiveListElement { _unsubscribe(_updatedSubItems); _streamController.close(); } + + Future reconnected() async { + if (loaded) { + _subscriptionQueue.whenComplete(() async { + await _updateSubItems(_object, _updatedSubItems); +// _streamController?.add(_object?.clone(_object?.toJson(full: true))); + }); + } + } + + List _getIncludeList(Map path) { + final List includes = []; + for (PathKey key in path.keys) { + includes.add(key.key); + includes.addAll( + _getIncludeList(path[key]).map((String e) => '${key.key}.$e')); + } + return includes; + } + + Future _updateSubItems( + ParseObject root, Map path) async { + final List> tasks = >[]; + for (PathKey key in path.keys) { + ParseObject subObject = root.get(key.key); + if (subObject?.containsKey(keyVarUpdatedAt) == true) { + final QueryBuilder queryBuilder = + QueryBuilder(subObject) + ..keysToReturn([keyVarUpdatedAt]) + ..whereEqualTo(keyVarObjectId, subObject.objectId); + ParseResponse parseResponse = await queryBuilder.query(); + if (parseResponse.success && + (parseResponse.results.first as ParseObject).updatedAt != + subObject.updatedAt) { + queryBuilder.limiters.remove("keys"); + queryBuilder.includeObject(_getIncludeList(path[key])); + ParseResponse parseResponse = await queryBuilder.query(); + if (parseResponse.success) { + subObject = parseResponse.result.first; +// root.getObjectData()[key.key] = subObject; + if (key.subscription?.eventCallbacks?.containsKey("update") == + true) { + key.subscription.eventCallbacks["update"](subObject); + } +// key.subscription.eventCallbacks["update"](subObject); + break; + } + } + } + tasks.add(_updateSubItems(subObject, path[key])); + } + await Future.wait(tasks); + } +} + +class PathKey { + PathKey(this.key, {this.subscription}); + + final String key; + Subscription subscription; + @override + String toString() { + return 'PathKey(key: $key, subscription: ${subscription?.requestId})'; + } } abstract class ParseLiveListEvent {