LCOV - code coverage report
Current view: top level - repository - remote_adapter_watch.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 92 94 97.9 %
Date: 2021-06-18 12:41:16 Functions: 0 0 -

          Line data    Source code
       1             : part of flutter_data;
       2             : 
       3             : mixin _RemoteAdapterWatch<T extends DataModel<T>> on _RemoteAdapter<T> {
       4           0 :   @protected
       5             :   @visibleForTesting
       6             :   Duration get throttleDuration =>
       7             :       const Duration(milliseconds: 16); // 1 frame at 60fps
       8             : 
       9           1 :   @protected
      10             :   @visibleForTesting
      11             :   DelayedStateNotifier<List<DataGraphEvent>> get throttledGraph =>
      12           4 :       graph.throttle(() => throttleDuration);
      13             : 
      14           1 :   @protected
      15             :   @visibleForTesting
      16             :   DataStateNotifier<List<T>> watchAll({
      17             :     bool? remote,
      18             :     Map<String, dynamic>? params,
      19             :     Map<String, String>? headers,
      20             :     bool? syncLocal,
      21             :     bool Function(T)? filterLocal,
      22             :   }) {
      23           1 :     _assertInit();
      24           1 :     remote ??= _remote;
      25             :     syncLocal ??= false;
      26           1 :     filterLocal ??= (_) => true;
      27             : 
      28           1 :     final _notifier = DataStateNotifier<List<T>>(
      29           2 :       data: DataState(localAdapter
      30           1 :           .findAll()
      31           1 :           .where(filterLocal)
      32           3 :           .map((m) => initializeModel(m, save: true))
      33           1 :           .filterNulls
      34           1 :           .toList()),
      35           1 :       reload: (notifier) async {
      36           1 :         if (!notifier.mounted) {
      37             :           return;
      38             :         }
      39             :         try {
      40           1 :           final _future = findAll(
      41             :             params: params,
      42             :             headers: headers,
      43             :             remote: remote,
      44             :             syncLocal: syncLocal,
      45             :             filterLocal: filterLocal,
      46             :           );
      47             :           if (remote!) {
      48           1 :             notifier.updateWith(isLoading: true);
      49             :           }
      50           1 :           await _future;
      51             :           // trigger doneLoading to ensure state is updated with isLoading=false
      52           4 :           graph._notify([internalType], DataGraphEventType.doneLoading);
      53           1 :         } on DataException catch (e) {
      54             :           // we're only interested in notifying errors
      55             :           // as models will pop up via the graph notifier
      56             :           // (we can catch the exception as we are NOT supplying
      57             :           // an `onError` to `findAll`)
      58           1 :           if (notifier.mounted) {
      59           1 :             notifier.updateWith(isLoading: false, exception: e);
      60             :           } else {
      61             :             rethrow;
      62             :           }
      63             :         }
      64             :       },
      65             :     );
      66             : 
      67             :     // kick off
      68           1 :     _notifier.reload();
      69             : 
      70           3 :     final _graphNotifier = throttledGraph.forEach((events) {
      71           1 :       if (!_notifier.mounted) {
      72             :         return;
      73             :       }
      74             : 
      75             :       final models =
      76           4 :           localAdapter.findAll().where(filterLocal!).toImmutableList();
      77             :       final modelChanged =
      78           3 :           !const DeepCollectionEquality().equals(models, _notifier.data.model);
      79             :       // ensure the done signal belongs to this notifier
      80           2 :       final doneLoading = events.singleWhereOrNull((e) =>
      81           2 :               e.type == DataGraphEventType.doneLoading &&
      82           4 :               e.keys.first == internalType) !=
      83             :           null;
      84             :       if (modelChanged || doneLoading) {
      85           1 :         _notifier.updateWith(model: models, isLoading: false, exception: null);
      86             :       }
      87             :     });
      88             : 
      89           2 :     _notifier.onDispose = _graphNotifier.dispose;
      90             :     return _notifier;
      91             :   }
      92             : 
      93           1 :   @protected
      94             :   @visibleForTesting
      95             :   DataStateNotifier<T?> watchOne(
      96             :     dynamic model, {
      97             :     bool? remote,
      98             :     Map<String, dynamic>? params,
      99             :     Map<String, String>? headers,
     100             :     AlsoWatch<T>? alsoWatch,
     101             :   }) {
     102           1 :     _assertInit();
     103             :     if (model == null) {
     104           0 :       throw AssertionError();
     105             :     }
     106           1 :     remote ??= _remote;
     107             : 
     108           1 :     final id = _resolveId(model);
     109             : 
     110             :     // lazy key access
     111           1 :     String? key() {
     112           3 :       return graph.getKeyForId(internalType, id,
     113           2 :           keyIfAbsent: (model is T ? model._key : null));
     114             :     }
     115             : 
     116           1 :     var _key = key();
     117             : 
     118             :     final _alsoWatchFilters = <String>{};
     119             : 
     120           2 :     final localModel = _key != null ? localAdapter.findOne(_key) : null;
     121           1 :     final _notifier = DataStateNotifier<T?>(
     122           1 :       data: DataState(
     123           1 :           localModel == null ? null : initializeModel(localModel, save: true)),
     124           1 :       reload: (notifier) async {
     125           1 :         if (!notifier.mounted) {
     126             :           return;
     127             :         }
     128             :         try {
     129             :           if (id != null) {
     130           1 :             final _future = findOne(
     131             :               id,
     132             :               params: params,
     133             :               headers: headers,
     134             :               remote: remote,
     135             :             );
     136             :             if (remote!) {
     137           1 :               notifier.updateWith(isLoading: true);
     138             :             }
     139           1 :             await _future;
     140             :           }
     141             : 
     142           1 :           _key ??= key();
     143             :           if (_key != null) {
     144             :             // trigger doneLoading to ensure state is updated with isLoading=false
     145           3 :             graph._notify([_key!], DataGraphEventType.doneLoading);
     146             :           }
     147           1 :         } on DataException catch (e) {
     148             :           // we're only interested in notifying errors
     149             :           // as models will pop up via the graph notifier
     150             :           // (we can catch the exception as we are NOT supplying
     151             :           // an `onError` to `findOne`)
     152           1 :           if (notifier.mounted) {
     153           1 :             notifier.updateWith(isLoading: false, exception: e);
     154             :           } else {
     155             :             rethrow;
     156             :           }
     157             :         }
     158             :       },
     159             :     );
     160             : 
     161           1 :     void _initializeRelationshipsToWatch(T? model) {
     162             :       if (alsoWatch != null &&
     163           1 :           _alsoWatchFilters.isEmpty &&
     164             :           model != null &&
     165           1 :           model.isInitialized) {
     166           4 :         _alsoWatchFilters.addAll(alsoWatch(model).map((rel) {
     167           1 :           return rel._name;
     168             :         }));
     169             :       }
     170             :     }
     171             : 
     172             :     // kick off
     173             : 
     174             :     // try to find relationships to watch
     175           3 :     _initializeRelationshipsToWatch(_notifier.data.model);
     176             : 
     177             :     // trigger local + async loading
     178           1 :     _notifier.reload();
     179             : 
     180             :     // start listening to graph for further changes
     181           3 :     final _graphNotifier = throttledGraph.forEach((events) {
     182           1 :       if (!_notifier.mounted) {
     183             :         return;
     184             :       }
     185             : 
     186             :       // buffers
     187           2 :       var modelBuffer = _notifier.data.model;
     188             :       var refresh = false;
     189             : 
     190           2 :       for (final event in events) {
     191           1 :         _key ??= key();
     192           2 :         if (_key != null && event.keys.containsFirst(_key!)) {
     193             :           // add/update
     194           2 :           if (event.type == DataGraphEventType.addNode ||
     195           2 :               event.type == DataGraphEventType.updateNode) {
     196           2 :             final model = localAdapter.findOne(_key!);
     197             :             if (model != null) {
     198           1 :               initializeModel(model, save: true);
     199           1 :               _initializeRelationshipsToWatch(model);
     200             :               modelBuffer = model;
     201             :             }
     202             :           }
     203             : 
     204             :           // remove
     205           2 :           if (event.type == DataGraphEventType.removeNode &&
     206           2 :               _notifier.data.model != null) {
     207             :             modelBuffer = null;
     208             :           }
     209             : 
     210             :           // changes on specific relationships of this model
     211           2 :           if (_notifier.data.model != null &&
     212           2 :               event.type.isEdge &&
     213           2 :               _alsoWatchFilters.contains(event.metadata)) {
     214             :             // calculate currently related models
     215             :             refresh = true;
     216             :           }
     217             : 
     218             :           if (modelBuffer != null &&
     219             :               // ensure the done signal belongs to this type
     220           3 :               event.keys.first == _key &&
     221           2 :               event.type == DataGraphEventType.doneLoading) {
     222             :             refresh = true;
     223             :           }
     224             :         }
     225             : 
     226             :         // updates on all models of specific relationships of this model
     227           2 :         if (event.type == DataGraphEventType.updateNode &&
     228           6 :             _relatedKeys(_notifier.data.model!).any(event.keys.contains)) {
     229             :           refresh = true;
     230             :         }
     231             :       }
     232             : 
     233             :       // NOTE: because of this comparison, use field equality
     234             :       // rather than key equality (which wouldn't update)
     235           3 :       if (modelBuffer != _notifier.data.model || refresh) {
     236           1 :         _notifier.updateWith(
     237             :             model: modelBuffer, isLoading: false, exception: null);
     238             :       }
     239             :     });
     240             : 
     241           2 :     _notifier.onDispose = _graphNotifier.dispose;
     242             :     return _notifier;
     243             :   }
     244             : 
     245           1 :   Set<String> _relatedKeys(T model) {
     246           1 :     return localAdapter
     247           1 :         .relationshipsFor(model)
     248           1 :         .values
     249           4 :         .map((meta) => (meta['instance'] as Relationship?)?.keys)
     250           1 :         .filterNulls
     251           2 :         .expand((key) => key)
     252           1 :         .toSet();
     253             :   }
     254             : }
     255             : 
     256             : typedef AlsoWatch<T> = List<Relationship> Function(T);

Generated by: LCOV version 1.15