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

Generated by: LCOV version 1.15