LCOV - code coverage report
Current view: top level - repository - remote_adapter_watch.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 99 99 100.0 %
Date: 2022-05-06 22:54:19 Functions: 0 0 -

          Line data    Source code
       1             : part of flutter_data;
       2             : 
       3             : mixin _RemoteAdapterWatch<T extends DataModel<T>> on _RemoteAdapter<T> {
       4           1 :   @protected
       5             :   DataStateNotifier<List<T>?> watchAllNotifier({
       6             :     bool? remote,
       7             :     Map<String, dynamic>? params,
       8             :     Map<String, String>? headers,
       9             :     bool? syncLocal,
      10             :     String? finder,
      11             :     DataRequestLabel? label,
      12             :   }) {
      13           1 :     remote ??= _remote;
      14             :     syncLocal ??= false;
      15             : 
      16           3 :     final _maybeFinder = _internalHolder?.finders[finder]?.call(this);
      17           2 :     final _finder = _maybeFinder is DataFinderAll<T> ? _maybeFinder : findAll;
      18             : 
      19             :     // we can't use `findAll`'s default internal label
      20             :     // because we need a label to handle events
      21           2 :     label ??= DataRequestLabel('watchAll', type: internalType);
      22             : 
      23           1 :     log(label, 'initializing');
      24             : 
      25             :     // closure to get latest models
      26           1 :     List<T>? _getUpdatedModels() {
      27           2 :       return localAdapter.findAll();
      28             :     }
      29             : 
      30           1 :     final _notifier = DataStateNotifier<List<T>?>(
      31           2 :       data: DataState(_getUpdatedModels(), isLoading: remote!),
      32             :     );
      33             : 
      34           2 :     _notifier._reloadFn = () async {
      35           1 :       if (!_notifier.mounted) {
      36             :         return;
      37             :       }
      38             : 
      39             :       if (remote!) {
      40           1 :         _notifier.updateWith(isLoading: true);
      41             :       }
      42             : 
      43           2 :       await _finder(
      44             :         remote: remote,
      45             :         params: params,
      46             :         headers: headers,
      47             :         syncLocal: syncLocal,
      48             :         label: label,
      49           1 :         onError: (e, label, _) async {
      50             :           try {
      51           2 :             await onError<List<T>>(e, label);
      52           1 :           } on DataException catch (err) {
      53             :             e = err;
      54             :           } catch (_) {
      55             :             rethrow;
      56             :           }
      57           1 :           if (_notifier.mounted) {
      58           1 :             _notifier.updateWith(isLoading: false, exception: e);
      59             :           }
      60             :           return null;
      61             :         },
      62             :       );
      63             :       if (remote) {
      64             :         // trigger doneLoading to ensure state is updated with isLoading=false
      65           4 :         graph._notify([label.toString()], type: DataGraphEventType.doneLoading);
      66             :       }
      67             :     };
      68             : 
      69             :     // kick off
      70           1 :     _notifier.reload();
      71             : 
      72           3 :     final _dispose = graph.addListener((event) {
      73           1 :       if (!_notifier.mounted) {
      74             :         return;
      75             :       }
      76             : 
      77             :       // handle done loading
      78           2 :       if (_notifier.data.isLoading &&
      79           4 :           event.keys.last == label.toString() &&
      80           2 :           event.type == DataGraphEventType.doneLoading) {
      81           1 :         final models = _getUpdatedModels();
      82           1 :         _notifier.updateWith(model: models, isLoading: false, exception: null);
      83             :       }
      84             : 
      85           3 :       if (_notifier.data.isLoading == false &&
      86           2 :           event.type.isNode &&
      87           4 :           event.keys.first.startsWith(internalType)) {
      88           1 :         final models = _getUpdatedModels();
      89           1 :         log(label!, 'updated models');
      90           1 :         _notifier.updateWith(model: models);
      91             :       }
      92             :     });
      93             : 
      94           2 :     _notifier.onDispose = () {
      95           1 :       log(label!, 'disposing');
      96           1 :       _dispose();
      97             :     };
      98             :     return _notifier;
      99             :   }
     100             : 
     101           1 :   @protected
     102             :   DataStateNotifier<T?> watchOneNotifier(
     103             :     String key, {
     104             :     bool? remote,
     105             :     Map<String, dynamic>? params,
     106             :     Map<String, String>? headers,
     107             :     AlsoWatch<T>? alsoWatch,
     108             :     String? finder,
     109             :     DataRequestLabel? label,
     110             :   }) {
     111           2 :     final id = graph.getIdForKey(key);
     112             : 
     113           1 :     remote ??= _remote;
     114           4 :     final _maybeFinder = _internalHolder?.finders[finder]?.call(this);
     115           2 :     final _finder = _maybeFinder is DataFinderOne<T> ? _maybeFinder : findOne;
     116             : 
     117             :     // we can't use `findOne`'s default internal label
     118             :     // because we need a label to handle events
     119             :     label ??=
     120           3 :         DataRequestLabel('watchOne', id: key.detypify(), type: internalType);
     121             : 
     122             :     var _alsoWatchPairs = <List<String>>{};
     123             : 
     124             :     // closure to get latest model and watchable relationship pairs
     125           1 :     T? _getUpdatedModel({DataStateNotifier<T?>? withNotifier}) {
     126           2 :       final model = localAdapter.findOne(key);
     127             :       if (model != null) {
     128             :         _alsoWatchPairs = {
     129           5 :           ...?alsoWatch?.call(model).filterNulls.map((r) {
     130           5 :             return r.keys.map((key) => [r._ownerKey!, key]);
     131           2 :           }).expand((_) => _)
     132             :         };
     133             :         if (withNotifier != null) {
     134           1 :           model._updateNotifier(withNotifier);
     135             :         }
     136             :       } else {
     137             :         // if there is no model nothing should be watched, reset pairs
     138             :         _alsoWatchPairs = {};
     139             :       }
     140             :       return model;
     141             :     }
     142             : 
     143           1 :     final _notifier = DataStateNotifier<T?>(
     144           2 :       data: DataState(_getUpdatedModel(), isLoading: remote!),
     145             :     );
     146             : 
     147           1 :     log(label,
     148           1 :         'initializing${alsoWatch != null ? ' (with relationships)' : ''}');
     149             : 
     150           2 :     _notifier._reloadFn = () async {
     151           1 :       if (!_notifier.mounted || id == null) return;
     152             : 
     153             :       if (remote!) {
     154           1 :         _notifier.updateWith(isLoading: true);
     155             :       }
     156             : 
     157           2 :       final model = await _finder(
     158             :         id,
     159             :         remote: remote,
     160             :         params: params,
     161             :         headers: headers,
     162             :         label: label,
     163           1 :         onError: (e, label, _) async {
     164             :           try {
     165           1 :             await onError<T>(e, label);
     166           1 :           } on DataException catch (err) {
     167             :             e = err;
     168             :           } catch (_) {
     169             :             rethrow;
     170             :           }
     171           1 :           if (_notifier.mounted) {
     172           1 :             _notifier.updateWith(isLoading: false, exception: e);
     173             :           }
     174             :           return null;
     175             :         },
     176             :       );
     177             :       // trigger doneLoading to ensure state is updated with isLoading=false
     178           1 :       final _key = model?._key;
     179             :       if (remote && _key != null) {
     180           4 :         graph._notify([_key, label.toString()],
     181             :             type: DataGraphEventType.doneLoading);
     182             :       }
     183             :     };
     184             : 
     185             :     // trigger local + async loading
     186           1 :     _notifier.reload();
     187             : 
     188             :     // local buffer useful to reduce amount of notifier updates
     189           2 :     var _model = _notifier.data.model;
     190             : 
     191             :     // start listening to graph for further changes
     192           3 :     final _dispose = graph.addListener((event) {
     193           1 :       if (!_notifier.mounted) return;
     194             : 
     195           1 :       final _key = _model?._key ?? key;
     196             : 
     197             :       // get the latest updated model with watchable relationships
     198             :       // (_alsoWatchPairs) in order to determine whether there is
     199             :       // something that will cause an event (with the introduction
     200             :       // of `andEach` even seemingly unrelated models could trigger)
     201           1 :       _model = _getUpdatedModel(withNotifier: _notifier);
     202             : 
     203           2 :       if (event.keys.contains(_key)) {
     204             :         // handle done loading
     205           2 :         if (_notifier.data.isLoading &&
     206           4 :             event.keys.last == label.toString() &&
     207           2 :             event.type == DataGraphEventType.doneLoading) {
     208           1 :           _notifier.updateWith(
     209             :               model: _model, isLoading: false, exception: null);
     210             :         }
     211             : 
     212             :         // add/update
     213           2 :         if (event.type == DataGraphEventType.addNode ||
     214           2 :             event.type == DataGraphEventType.updateNode) {
     215           3 :           if (_notifier.data.isLoading == false) {
     216           3 :             log(label!, 'added/updated node ${event.keys}');
     217           1 :             _notifier.updateWith(model: _model);
     218             :           }
     219             :         }
     220             : 
     221             :         // temporarily restore removed pair so that watchedRelationshipUpdate
     222             :         // has a chance to apply the update
     223           2 :         if (event.type == DataGraphEventType.removeEdge &&
     224           3 :             !event.keys.first.startsWith('id:')) {
     225           2 :           _alsoWatchPairs.add(event.keys);
     226             :         }
     227             :       }
     228             : 
     229             :       // handle deletion
     230           2 :       if (event.type == DataGraphEventType.removeNode && _model == null) {
     231           3 :         log(label!, 'removed node ${event.keys}');
     232           1 :         _notifier.updateWith(model: null);
     233             :       }
     234             : 
     235             :       // updates on watched relationships condition
     236           2 :       final watchedRelationshipUpdate = event.type.isEdge &&
     237             :           _alsoWatchPairs
     238           2 :               .where((pair) =>
     239           6 :                   pair.sorted().toString() == event.keys.sorted().toString())
     240           1 :               .isNotEmpty;
     241             : 
     242             :       // updates on watched models (of relationships) condition
     243           2 :       final watchedModelUpdate = event.type.isNode &&
     244             :           _alsoWatchPairs
     245           5 :               .where((pair) => pair.contains(event.keys.first))
     246           1 :               .isNotEmpty;
     247             : 
     248             :       // if model is loaded and any condition passes, notify update
     249           3 :       if (_notifier.data.isLoading == false &&
     250             :           (watchedRelationshipUpdate || watchedModelUpdate)) {
     251           3 :         log(label!, 'relationship update ${event.keys}');
     252           1 :         _notifier.updateWith(model: _model);
     253             :       }
     254             :     });
     255             : 
     256           2 :     _notifier.onDispose = () {
     257           1 :       log(label!, 'disposing');
     258           1 :       _dispose();
     259             :     };
     260             :     return _notifier;
     261             :   }
     262             : }

Generated by: LCOV version 1.15