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

          Line data    Source code
       1             : part of flutter_data;
       2             : 
       3             : /// Repository is the API used to interact with models whether local or remote.
       4             : class Repository<T extends DataModel<T>> with _Lifecycle {
       5             :   final Reader _read;
       6          11 :   Repository(this._read);
       7             : 
       8             :   var _isInit = false;
       9             : 
      10          22 :   String get _internalType => DataHelpers.getType<T>();
      11             : 
      12             :   final _adapters = <String, RemoteAdapter>{};
      13             : 
      14             :   /// Obtain the [RemoteAdapter] for this type.
      15          11 :   RemoteAdapter<T> get remoteAdapter =>
      16          33 :       _adapters[_internalType]! as RemoteAdapter<T>;
      17             : 
      18             :   /// Type for the [RemoteAdapter]
      19           1 :   @nonVirtual
      20           2 :   String get type => remoteAdapter.type;
      21             : 
      22             :   /// Initializes this [Repository]. Nothing will work without this.
      23             :   /// In standard scenarios this initialization is done by the framework.
      24             :   @mustCallSuper
      25          11 :   FutureOr<Repository<T>> initialize(
      26             :       {bool? remote, required Map<String, RemoteAdapter> adapters}) async {
      27          11 :     if (isInitialized) return this;
      28          22 :     _adapters.addAll(adapters);
      29          33 :     await remoteAdapter.initialize(
      30             :       remote: remote,
      31             :       adapters: adapters,
      32          11 :       read: _read,
      33             :     );
      34          11 :     _isInit = true;
      35             :     return this;
      36             :   }
      37             : 
      38             :   /// Whether this [Repository] is initialized
      39             :   /// (when its underlying [RemoteAdapter] is).
      40          11 :   @override
      41          31 :   bool get isInitialized => _isInit && remoteAdapter.isInitialized;
      42             : 
      43             :   /// Disposes this [Repository] and everything that depends on it.
      44          10 :   @override
      45             :   void dispose() {
      46          10 :     if (isInitialized) {
      47          20 :       remoteAdapter.dispose();
      48          10 :       _isInit = false;
      49             :     }
      50             :   }
      51             : 
      52             :   // Public API
      53             : 
      54             :   /// Returns all models of type [T].
      55             :   ///
      56             :   /// If [_RemoteAdapter.shouldLoadRemoteAll] (function of [remote]) is `true`,
      57             :   /// it will initiate an HTTP call.
      58             :   /// Otherwise returns all models of type [T] in local storage.
      59             :   ///
      60             :   /// Arguments [params] and [headers] will be merged with
      61             :   /// [_RemoteAdapter.defaultParams] and [_RemoteAdapter.defaultHeaders], respectively.
      62             :   ///
      63             :   /// For local storage of type [T] to be synchronized to the exact resources
      64             :   /// returned from the remote source when using `findAll`, pass `syncLocal: true`.
      65             :   /// This call would, for example, reflect server-side resource deletions.
      66             :   /// The default is `syncLocal: false`.
      67             :   ///
      68             :   /// See also: [_RemoteAdapter.urlForFindAll], [_RemoteAdapter.methodForFindAll].
      69           2 :   Future<List<T>?> findAll({
      70             :     bool? remote,
      71             :     bool? background,
      72             :     Map<String, dynamic>? params,
      73             :     Map<String, String>? headers,
      74             :     bool? syncLocal,
      75             :     OnSuccessAll<T>? onSuccess,
      76             :     OnErrorAll<T>? onError,
      77             :     DataRequestLabel? label,
      78             :   }) {
      79           4 :     return remoteAdapter.findAll(
      80             :       remote: remote,
      81             :       background: background,
      82             :       params: params,
      83             :       headers: headers,
      84             :       syncLocal: syncLocal,
      85             :       onSuccess: onSuccess,
      86             :       onError: onError,
      87             :       label: label,
      88             :     );
      89             :   }
      90             : 
      91             :   /// Returns model of type [T] by [id].
      92             :   ///
      93             :   /// If [_RemoteAdapter.shouldLoadRemoteOne] (function of [remote]) is `true`,
      94             :   /// it will initiate an HTTP call.
      95             :   /// Otherwise returns model of type [T] and [id] in local storage.
      96             :   ///
      97             :   /// Arguments [params] and [headers] will be merged with
      98             :   /// [_RemoteAdapter.defaultParams] and [_RemoteAdapter.defaultHeaders], respectively.
      99             :   ///
     100             :   /// See also: [_RemoteAdapter.urlForFindOne], [_RemoteAdapter.methodForFindOne].
     101           4 :   Future<T?> findOne(
     102             :     Object id, {
     103             :     bool? remote,
     104             :     bool? background,
     105             :     Map<String, dynamic>? params,
     106             :     Map<String, String>? headers,
     107             :     OnSuccessOne<T>? onSuccess,
     108             :     OnErrorOne<T>? onError,
     109             :     DataRequestLabel? label,
     110             :   }) {
     111           8 :     return remoteAdapter.findOne(
     112             :       id,
     113             :       remote: remote,
     114             :       background: background,
     115             :       params: params,
     116             :       headers: headers,
     117             :       onSuccess: onSuccess,
     118             :       onError: onError,
     119             :       label: label,
     120             :     );
     121             :   }
     122             : 
     123             :   /// Saves [model] of type [T].
     124             :   ///
     125             :   /// If [remote] is `true`, it will initiate an HTTP call.
     126             :   ///
     127             :   /// Always persists to local storage.
     128             :   ///
     129             :   /// Arguments [params] and [headers] will be merged with
     130             :   /// [_RemoteAdapter.defaultParams] and [_RemoteAdapter.defaultHeaders], respectively.
     131             :   ///
     132             :   /// See also: [_RemoteAdapter.urlForSave], [_RemoteAdapter.methodForSave].
     133           4 :   Future<T> save(
     134             :     T model, {
     135             :     bool? remote,
     136             :     Map<String, dynamic>? params,
     137             :     Map<String, String>? headers,
     138             :     OnSuccessOne<T>? onSuccess,
     139             :     OnErrorOne<T>? onError,
     140             :     DataRequestLabel? label,
     141             :   }) {
     142           8 :     return remoteAdapter.save(
     143             :       model,
     144             :       remote: remote,
     145             :       params: params,
     146             :       headers: headers,
     147             :       onSuccess: onSuccess,
     148             :       onError: onError,
     149             :       label: label,
     150             :     );
     151             :   }
     152             : 
     153             :   /// Deletes [model] of type [T].
     154             :   ///
     155             :   /// If [remote] is `true`, it will initiate an HTTP call.
     156             :   ///
     157             :   /// Always deletes from local storage.
     158             :   ///
     159             :   /// Arguments [params] and [headers] will be merged with
     160             :   /// [_RemoteAdapter.defaultParams] and [_RemoteAdapter.defaultHeaders], respectively.
     161             :   ///
     162             :   /// See also: [_RemoteAdapter.urlForDelete], [_RemoteAdapter.methodForDelete].
     163           1 :   Future<T?> delete(
     164             :     Object model, {
     165             :     bool? remote,
     166             :     Map<String, dynamic>? params,
     167             :     Map<String, String>? headers,
     168             :     OnSuccessOne<T>? onSuccess,
     169             :     OnErrorOne<T>? onError,
     170             :     DataRequestLabel? label,
     171             :   }) {
     172           2 :     return remoteAdapter.delete(
     173             :       model,
     174             :       remote: remote,
     175             :       params: params,
     176             :       headers: headers,
     177             :       onSuccess: onSuccess,
     178             :       onError: onError,
     179             :       label: label,
     180             :     );
     181             :   }
     182             : 
     183             :   /// Deletes all models of type [T] in local storage.
     184             :   ///
     185             :   /// If you need to clear all models, use the
     186             :   /// `repositoryProviders` map exposed on your `main.data.dart`.
     187           3 :   Future<void> clear() => remoteAdapter.clear();
     188             : 
     189             :   // offline
     190             : 
     191             :   /// Gets a list of all pending [OfflineOperation]s for this type.
     192           1 :   Set<OfflineOperation<T>> get offlineOperations =>
     193           2 :       remoteAdapter.offlineOperations;
     194             : 
     195             :   // watchers
     196             : 
     197             :   /// Watches a provider wrapping [Repository.watchAllNotifier]
     198             :   /// which allows the watcher to be notified of changes
     199             :   /// on any model of this [type].
     200             :   ///
     201             :   /// Example: Watch all models of type `books` on a Riverpod hook-enabled app.
     202             :   ///
     203             :   /// ```
     204             :   /// ref.books.watchAll();
     205             :   /// ```
     206           1 :   DataState<List<T>?> watchAll({
     207             :     bool? remote,
     208             :     Map<String, dynamic>? params,
     209             :     Map<String, String>? headers,
     210             :     bool? syncLocal,
     211             :     String? finder,
     212             :     DataRequestLabel? label,
     213             :   }) {
     214           1 :     final provider = watchAllProvider(
     215             :       remote: remote,
     216             :       params: params,
     217             :       headers: headers,
     218             :       syncLocal: syncLocal,
     219             :       finder: finder,
     220             :       label: label,
     221             :     );
     222           3 :     return remoteAdapter.internalWatch!(provider);
     223             :   }
     224             : 
     225             :   /// Watches a provider wrapping [Repository.watchOneNotifier]
     226             :   /// which allows the watcher to be notified of changes
     227             :   /// on a specific model of this [type], optionally reacting
     228             :   /// to selected relationships of this model via [alsoWatch].
     229             :   ///
     230             :   /// Example: Watch model of type `books` and `id=1` along
     231             :   /// with its `author` relationship on a Riverpod hook-enabled app.
     232             :   ///
     233             :   /// ```
     234             :   /// ref.books.watchOne(1, alsoWatch: (book) => [book.author]);
     235             :   /// ```
     236           1 :   DataState<T?> watchOne(
     237             :     Object model, {
     238             :     bool? remote,
     239             :     Map<String, dynamic>? params,
     240             :     Map<String, String>? headers,
     241             :     AlsoWatch<T>? alsoWatch,
     242             :     String? finder,
     243             :     DataRequestLabel? label,
     244             :   }) {
     245           1 :     final provider = watchOneProvider(
     246             :       model,
     247             :       remote: remote,
     248             :       params: params,
     249             :       headers: headers,
     250             :       alsoWatch: alsoWatch,
     251             :       finder: finder,
     252             :       label: label,
     253             :     );
     254           3 :     return remoteAdapter.internalWatch!(provider);
     255             :   }
     256             : 
     257             :   // providers
     258             : 
     259           2 :   AutoDisposeStateNotifierProvider<DataStateNotifier<List<T>?>,
     260             :       DataState<List<T>?>> watchAllProvider({
     261             :     bool? remote,
     262             :     Map<String, dynamic>? params,
     263             :     Map<String, String>? headers,
     264             :     bool? syncLocal,
     265             :     String? finder,
     266             :     DataRequestLabel? label,
     267             :   }) {
     268           4 :     remote ??= remoteAdapter._remote;
     269           4 :     return _watchAllProvider(
     270           2 :       WatchArgs(
     271             :         remote: remote,
     272             :         params: params,
     273             :         headers: headers,
     274             :         syncLocal: syncLocal,
     275             :         finder: finder,
     276             :         label: label,
     277             :       ),
     278             :     );
     279             :   }
     280             : 
     281           2 :   late final _watchAllProvider = StateNotifierProvider.autoDispose
     282           4 :       .family<DataStateNotifier<List<T>?>, DataState<List<T>?>, WatchArgs<T>>(
     283           2 :           (ref, args) {
     284           4 :     return remoteAdapter.watchAllNotifier(
     285           2 :       remote: args.remote,
     286           2 :       params: args.params,
     287           2 :       headers: args.headers,
     288           2 :       syncLocal: args.syncLocal,
     289           2 :       finder: args.finder,
     290           2 :       label: args.label,
     291             :     );
     292             :   });
     293             : 
     294           2 :   AutoDisposeStateNotifierProvider<DataStateNotifier<T?>, DataState<T?>>
     295             :       watchOneProvider(
     296             :     Object model, {
     297             :     bool? remote,
     298             :     Map<String, dynamic>? params,
     299             :     Map<String, String>? headers,
     300             :     AlsoWatch<T>? alsoWatch,
     301             :     String? finder,
     302             :     DataRequestLabel? label,
     303             :   }) {
     304           4 :     final key = remoteAdapter.keyForModelOrId(model);
     305           4 :     remote ??= remoteAdapter._remote;
     306             :     final relationshipMetas = alsoWatch
     307           2 :         ?.call(RelationshipGraphNode<T>())
     308           1 :         .whereType<RelationshipMeta>()
     309           1 :         .toImmutableList();
     310             : 
     311           4 :     return _watchOneProvider(
     312           2 :       WatchArgs(
     313             :         key: key,
     314             :         remote: remote,
     315             :         params: params,
     316             :         headers: headers,
     317             :         relationshipMetas: relationshipMetas,
     318             :         alsoWatch: alsoWatch,
     319             :         finder: finder,
     320             :         label: label,
     321             :       ),
     322             :     );
     323             :   }
     324             : 
     325           2 :   late final _watchOneProvider = StateNotifierProvider.autoDispose
     326           6 :       .family<DataStateNotifier<T?>, DataState<T?>, WatchArgs<T>>((ref, args) {
     327           4 :     return remoteAdapter.watchOneNotifier(
     328           2 :       args.key!,
     329           2 :       remote: args.remote,
     330           2 :       params: args.params,
     331           2 :       headers: args.headers,
     332           2 :       alsoWatch: args.alsoWatch,
     333           2 :       finder: args.finder,
     334           2 :       label: args.label,
     335             :     );
     336             :   });
     337             : 
     338             :   // notifiers
     339             : 
     340           2 :   DataStateNotifier<List<T>?> watchAllNotifier(
     341             :       {bool? remote,
     342             :       Map<String, dynamic>? params,
     343             :       Map<String, String>? headers,
     344             :       bool? syncLocal,
     345             :       String? finder,
     346             :       DataRequestLabel? label}) {
     347           2 :     final provider = watchAllProvider(
     348             :       remote: remote,
     349             :       params: params,
     350             :       headers: headers,
     351             :       syncLocal: syncLocal,
     352             :       finder: finder,
     353             :       label: label,
     354             :     );
     355           8 :     return remoteAdapter.internalWatch!(provider.notifier);
     356             :   }
     357             : 
     358           2 :   DataStateNotifier<T?> watchOneNotifier(Object model,
     359             :       {bool? remote,
     360             :       Map<String, dynamic>? params,
     361             :       Map<String, String>? headers,
     362             :       AlsoWatch<T>? alsoWatch,
     363             :       String? finder,
     364             :       DataRequestLabel? label}) {
     365           8 :     return remoteAdapter.internalWatch!(watchOneProvider(
     366             :       model,
     367             :       remote: remote,
     368             :       params: params,
     369             :       headers: headers,
     370             :       alsoWatch: alsoWatch,
     371             :       finder: finder,
     372             :       label: label,
     373           2 :     ).notifier);
     374             :   }
     375             : 
     376             :   /// Watch this model (local)
     377           1 :   T watch(T model) {
     378           2 :     return watchOne(model, remote: false).model!;
     379             :   }
     380             : 
     381             :   /// Notifier for watched model (local)
     382           1 :   DataStateNotifier<T?> notifierFor(T model) {
     383           1 :     return watchOneNotifier(model, remote: false);
     384             :   }
     385             : 
     386             :   /// Logs messages for a specific label when `verbose` is `true`.
     387           1 :   void log(DataRequestLabel label, String message, {int logLevel = 1}) {
     388           2 :     remoteAdapter.log(label, message, logLevel: logLevel);
     389             :   }
     390             : 
     391           0 :   int get logLevel => remoteAdapter._logLevel;
     392          11 :   set logLevel(int value) {
     393          22 :     remoteAdapter._logLevel = value;
     394             :   }
     395             : }
     396             : 
     397             : /// Annotation on a [DataModel] model to request a [Repository] be generated for it.
     398             : ///
     399             : /// Takes a list of [adapters] to be mixed into this [Repository].
     400             : /// Public methods of these [adapters] mixins will be made available in the repository
     401             : /// via extensions.
     402             : ///
     403             : /// A classic example is:
     404             : ///
     405             : /// ```
     406             : /// @JsonSerializable()
     407             : /// @DataRepository([JSONAPIAdapter])
     408             : /// class Todo with DataModel<Todo> {
     409             : ///   @override
     410             : ///   final int id;
     411             : ///   final String title;
     412             : ///   final bool completed;
     413             : ///
     414             : ///   Todo({this.id, this.title, this.completed = false});
     415             : /// }
     416             : ///```
     417             : class DataRepository {
     418             :   final List<Type> adapters;
     419             :   final bool remote;
     420             :   final int? typeId;
     421          71 :   const DataRepository(this.adapters, {this.remote = true, this.typeId});
     422             : }

Generated by: LCOV version 1.15