LCOV - code coverage report
Current view: top level - repository - remote_adapter_watch.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 142 144 98.6 %
Date: 2022-07-06 18:09:45 Functions: 0 0 -

          Line data    Source code
       1             : part of flutter_data;
       2             : 
       3             : mixin _RemoteAdapterWatch<T extends DataModel<T>> on _RemoteAdapter<T> {
       4           2 :   @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           0 :     remote ??= _remote;
      14             :     syncLocal ??= false;
      15             : 
      16           6 :     final maybeFinder = _internalHolder?.finders[finder]?.call(this);
      17           4 :     final finderFn = 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           4 :     label ??= DataRequestLabel('watchAll', type: internalType);
      22             : 
      23           2 :     log(label, 'initializing');
      24             : 
      25             :     // closure to get latest models
      26           2 :     List<T>? _getUpdatedModels() {
      27           4 :       return localAdapter.findAll();
      28             :     }
      29             : 
      30           2 :     final notifier = DataStateNotifier<List<T>?>(
      31           4 :       data: DataState(_getUpdatedModels(), isLoading: remote!),
      32             :     );
      33             : 
      34           4 :     notifier._reloadFn = () async {
      35           2 :       if (!notifier.mounted) {
      36             :         return;
      37             :       }
      38             : 
      39             :       if (remote!) {
      40           2 :         notifier.updateWith(isLoading: true);
      41             :       }
      42             : 
      43           4 :       await finderFn(
      44             :         remote: remote,
      45             :         params: params,
      46             :         headers: headers,
      47             :         syncLocal: syncLocal,
      48             :         label: label,
      49           2 :         onError: (e, label, _) async {
      50             :           try {
      51           3 :             await onError<List<T>>(e, label);
      52           1 :           } on DataException catch (err) {
      53             :             e = err;
      54             :           } catch (_) {
      55             :             rethrow;
      56             :           }
      57           2 :           if (notifier.mounted) {
      58           2 :             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           8 :         graph._notify([label.toString()], type: DataGraphEventType.doneLoading);
      66             :       }
      67             :     };
      68             : 
      69             :     // kick off
      70           2 :     notifier.reload();
      71             : 
      72           6 :     final throttleDuration = read(graphNotifierThrottleDurationProvider);
      73           6 :     final throttledGraph = graph.throttle(() => throttleDuration);
      74             : 
      75           2 :     final states = <DataState<List<T>?>>[];
      76             : 
      77           4 :     final dispose = throttledGraph.addListener((events) {
      78           2 :       if (!notifier.mounted) {
      79             :         return;
      80             :       }
      81             : 
      82           4 :       for (final event in events) {
      83             :         // handle done loading
      84           4 :         if (notifier.data.isLoading &&
      85           8 :             event.keys.last == label.toString() &&
      86           4 :             event.type == DataGraphEventType.doneLoading) {
      87           2 :           final models = _getUpdatedModels();
      88           4 :           states.add(DataState(models, isLoading: false, exception: null));
      89             :         }
      90             : 
      91           6 :         if (notifier.data.isLoading == false &&
      92           4 :             event.type.isNode &&
      93           8 :             event.keys.first.startsWith(internalType)) {
      94           2 :           final models = _getUpdatedModels();
      95           2 :           log(label!, 'updated models', logLevel: 2);
      96           4 :           states.add(DataState(
      97             :             models,
      98           4 :             isLoading: notifier.data.isLoading,
      99           4 :             exception: notifier.data.exception,
     100           4 :             stackTrace: notifier.data.stackTrace,
     101             :           ));
     102             :         }
     103             :       }
     104             : 
     105           2 :       _updateFromStates(states, notifier);
     106             :     });
     107             : 
     108           4 :     notifier.onDispose = () {
     109           2 :       log(label!, 'disposing');
     110           2 :       dispose();
     111             :     };
     112             :     return notifier;
     113             :   }
     114             : 
     115           2 :   @protected
     116             :   DataStateNotifier<T?> watchOneNotifier(
     117             :     String key, {
     118             :     bool? remote,
     119             :     Map<String, dynamic>? params,
     120             :     Map<String, String>? headers,
     121             :     AlsoWatch<T>? alsoWatch,
     122             :     String? finder,
     123             :     DataRequestLabel? label,
     124             :   }) {
     125           4 :     final id = graph.getIdForKey(key);
     126             : 
     127           0 :     remote ??= _remote;
     128           7 :     final maybeFinder = _internalHolder?.finders[finder]?.call(this);
     129           4 :     final finderFn = maybeFinder is DataFinderOne<T> ? maybeFinder : findOne;
     130             : 
     131             :     // we can't use `findOne`'s default internal label
     132             :     // because we need a label to handle events
     133             :     label ??=
     134           6 :         DataRequestLabel('watchOne', id: key.detypify(), type: internalType);
     135             : 
     136             :     var alsoWatchPairs = <List<String>>{};
     137             : 
     138             :     // closure to get latest model and watchable relationship pairs
     139           2 :     T? _getUpdatedModel() {
     140           4 :       final model = localAdapter.findOne(key);
     141             :       if (model != null) {
     142             :         // get all metas provided via `alsoWatch`
     143             :         final metas = alsoWatch
     144           2 :             ?.call(RelationshipGraphNode<T>())
     145           1 :             .whereType<RelationshipMeta>();
     146             : 
     147             :         // recursively get applicable watch key pairs for each meta -
     148             :         // from top to bottom (e.g. `p`, `p.familia`, `p.familia.cottage`)
     149             :         alsoWatchPairs = {
     150           1 :           ...?metas
     151           4 :               ?.map((meta) => _getPairsForMeta(meta._top, model))
     152           1 :               .filterNulls
     153           2 :               .expand((_) => _)
     154             :         };
     155             :       } else {
     156             :         // if there is no model nothing should be watched, reset pairs
     157             :         alsoWatchPairs = {};
     158             :       }
     159             :       return model;
     160             :     }
     161             : 
     162           2 :     final notifier = DataStateNotifier<T?>(
     163           4 :       data: DataState(_getUpdatedModel(), isLoading: remote!),
     164             :     );
     165             : 
     166             :     final alsoWatchNames = alsoWatch
     167           2 :             ?.call(RelationshipGraphNode<T>())
     168           1 :             .whereType<RelationshipMeta>()
     169           3 :             .map((m) => m.name) ??
     170             :         {};
     171           2 :     log(label,
     172           6 :         'initializing${alsoWatchNames.isNotEmpty ? ' (and also watching: ${alsoWatchNames.join(', ')})' : ''}');
     173             : 
     174           4 :     notifier._reloadFn = () async {
     175           2 :       if (!notifier.mounted || id == null) return;
     176             : 
     177             :       if (remote!) {
     178           1 :         notifier.updateWith(isLoading: true);
     179             :       }
     180             : 
     181           4 :       final model = await finderFn(
     182             :         id,
     183             :         remote: remote,
     184             :         params: params,
     185             :         headers: headers,
     186             :         label: label,
     187           1 :         onError: (e, label, _) async {
     188             :           try {
     189           1 :             await onError<T>(e, label);
     190           1 :           } on DataException catch (err) {
     191             :             e = err;
     192             :           } catch (_) {
     193             :             rethrow;
     194             :           }
     195           1 :           if (notifier.mounted) {
     196           1 :             notifier.updateWith(isLoading: false, exception: e);
     197             :           }
     198             :           return null;
     199             :         },
     200             :       );
     201             :       // trigger doneLoading to ensure state is updated with isLoading=false
     202           2 :       final modelKey = model?._key;
     203             :       if (remote && modelKey != null) {
     204           4 :         graph._notify([modelKey, label.toString()],
     205             :             type: DataGraphEventType.doneLoading);
     206             :       }
     207             :     };
     208             : 
     209             :     // trigger local + async loading
     210           2 :     notifier.reload();
     211             : 
     212             :     // local buffer useful to reduce amount of notifier updates
     213           4 :     var bufferModel = notifier.data.model;
     214             : 
     215           6 :     final throttleDuration = read(graphNotifierThrottleDurationProvider);
     216           6 :     final throttledGraph = graph.throttle(() => throttleDuration);
     217             : 
     218           2 :     final states = <DataState<T?>>[];
     219             : 
     220             :     // start listening to graph for further changes
     221           4 :     final dispose = throttledGraph.addListener((events) {
     222           2 :       if (!notifier.mounted) return;
     223             : 
     224             :       // get the latest updated model with watchable relationships
     225             :       // (_alsoWatchPairs) in order to determine whether there is
     226             :       // something that will cause an event (with the introduction
     227             :       // of `andEach` even seemingly unrelated models could trigger)
     228           2 :       bufferModel = _getUpdatedModel();
     229             : 
     230           2 :       key = bufferModel?._key ?? key;
     231             : 
     232           4 :       for (final event in events) {
     233           4 :         if (event.keys.contains(key)) {
     234             :           // handle done loading
     235           4 :           if (notifier.data.isLoading &&
     236           4 :               event.keys.last == label.toString() &&
     237           2 :               event.type == DataGraphEventType.doneLoading) {
     238             :             states
     239           2 :                 .add(DataState(bufferModel, isLoading: false, exception: null));
     240             :           }
     241             : 
     242             :           // add/update
     243           4 :           if (event.type == DataGraphEventType.addNode ||
     244           4 :               event.type == DataGraphEventType.updateNode) {
     245           6 :             if (notifier.data.isLoading == false) {
     246           6 :               log(label!, 'added/updated node ${event.keys}', logLevel: 2);
     247           4 :               states.add(DataState(
     248             :                 bufferModel,
     249           4 :                 isLoading: notifier.data.isLoading,
     250           4 :                 exception: notifier.data.exception,
     251           4 :                 stackTrace: notifier.data.stackTrace,
     252             :               ));
     253             :             }
     254             :           }
     255             : 
     256             :           // temporarily restore removed pair so that watchedRelationshipUpdate
     257             :           // has a chance to apply the update
     258           4 :           if (event.type == DataGraphEventType.removeEdge &&
     259           3 :               !event.keys.first.startsWith('id:')) {
     260           2 :             alsoWatchPairs.add(event.keys);
     261             :           }
     262             :         }
     263             : 
     264             :         // handle deletion
     265           4 :         if (event.type == DataGraphEventType.removeNode &&
     266             :             bufferModel == null) {
     267           3 :           log(label!, 'removed node ${event.keys}', logLevel: 2);
     268           2 :           states.add(DataState(
     269             :             null,
     270           2 :             isLoading: notifier.data.isLoading,
     271           2 :             exception: notifier.data.exception,
     272           2 :             stackTrace: notifier.data.stackTrace,
     273             :           ));
     274             :         }
     275             : 
     276             :         // updates on watched relationships condition
     277           4 :         final watchedRelationshipUpdate = event.type.isEdge &&
     278             :             alsoWatchPairs
     279           2 :                 .where((pair) =>
     280           6 :                     pair.sorted().toString() == event.keys.sorted().toString())
     281           1 :                 .isNotEmpty;
     282             : 
     283             :         // updates on watched models (of relationships) condition
     284           4 :         final watchedModelUpdate = event.type.isNode &&
     285             :             alsoWatchPairs
     286           6 :                 .where((pair) => pair.contains(event.keys.first))
     287           2 :                 .isNotEmpty;
     288             : 
     289             :         // if model is loaded and any condition passes, notify update
     290           6 :         if (notifier.data.isLoading == false &&
     291             :             (watchedRelationshipUpdate || watchedModelUpdate)) {
     292           3 :           log(label!, 'relationship update ${event.keys}', logLevel: 2);
     293           2 :           states.add(DataState(
     294             :             bufferModel,
     295           2 :             isLoading: notifier.data.isLoading,
     296           2 :             exception: notifier.data.exception,
     297           2 :             stackTrace: notifier.data.stackTrace,
     298             :           ));
     299             :         }
     300             :       }
     301             : 
     302           2 :       _updateFromStates(states, notifier);
     303             :     });
     304             : 
     305           4 :     notifier.onDispose = () {
     306           2 :       log(label!, 'disposing');
     307           2 :       dispose();
     308             :     };
     309             :     return notifier;
     310             :   }
     311             : 
     312             :   // `S` could be `T` or `List<T>`
     313           3 :   void _updateFromStates<S>(
     314             :       List<DataState<S>> states, DataStateNotifier<S> notifier) {
     315             :     // calculate final state & drain
     316           6 :     final mergedState = states.fold<DataState<S>?>(null, (acc, state) {
     317           2 :       return acc == null ? state : acc.merge(state);
     318             :     });
     319           3 :     states.clear();
     320             : 
     321             :     if (mergedState != null) {
     322           3 :       notifier.updateWith(
     323           3 :         model: mergedState.model,
     324           3 :         isLoading: mergedState.isLoading,
     325           3 :         exception: mergedState.exception,
     326           3 :         stackTrace: mergedState.stackTrace,
     327             :       );
     328             :     }
     329             :   }
     330             : 
     331           1 :   Iterable<List<String>> _getPairsForMeta(
     332             :       RelationshipMeta? meta, DataModel model) {
     333             :     // get instance of this relationship
     334           2 :     final relationship = meta?.instance(model);
     335             :     if (relationship == null) return {};
     336             : 
     337             :     return {
     338             :       // include key pairs of (owner, key)
     339           3 :       for (final key in relationship._keys) [relationship._ownerKey!, key],
     340             :       // recursively include key pairs for other requested relationships
     341           1 :       for (final childModel in relationship._iterable)
     342           2 :         _getPairsForMeta(meta!.child, childModel as DataModel)
     343           2 :             .expand((_) => _)
     344           1 :             .toList()
     345             :     };
     346             :   }
     347             : }
     348             : 
     349           6 : final graphNotifierThrottleDurationProvider =
     350           6 :     Provider<Duration>((ref) => Duration.zero);

Generated by: LCOV version 1.15