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

          Line data    Source code
       1             : part of flutter_data;
       2             : 
       3             : /// Thin wrapper on the [RemoteAdapter] API
       4             : class Repository<T extends DataModel<T>> with _Lifecycle {
       5             :   final Reader _read;
       6           1 :   Repository(this._read);
       7             : 
       8             :   var _isInit = false;
       9             : 
      10           2 :   String get _internalType => DataHelpers.getType<T>();
      11             : 
      12             :   final _adapters = <String, RemoteAdapter>{};
      13             : 
      14             :   /// Obtain the [RemoteAdapter] for this type.
      15           1 :   RemoteAdapter<T> get remoteAdapter =>
      16           3 :       _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           1 :   FutureOr<Repository<T>> initialize(
      26             :       {bool? remote, required Map<String, RemoteAdapter> adapters}) async {
      27           1 :     if (isInitialized) return this;
      28           2 :     _adapters.addAll(adapters);
      29           3 :     await remoteAdapter.initialize(
      30             :       remote: remote,
      31             :       adapters: adapters,
      32           1 :       read: _read,
      33             :     );
      34           1 :     _isInit = true;
      35             :     return this;
      36             :   }
      37             : 
      38             :   /// Returns whether this [Repository] is initialized
      39             :   /// (when its underlying [RemoteAdapter] is).
      40           1 :   @override
      41           3 :   bool get isInitialized => _isInit && remoteAdapter.isInitialized;
      42             : 
      43             :   /// Disposes this [Repository] and everything that depends on it.
      44           1 :   @override
      45             :   void dispose() {
      46           1 :     if (isInitialized) {
      47           2 :       remoteAdapter.dispose();
      48           1 :       _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           1 :   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           2 :     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           1 :   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           2 :     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           1 :   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           2 :     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             :   ///
     186             :   ///
     187             :   /// If you need to clear all models, use the
     188             :   /// `repositoryProviders` map exposed on your `main.data.dart`.
     189           3 :   Future<void> clear() => remoteAdapter.clear();
     190             : 
     191             :   // offline
     192             : 
     193             :   /// Gets a list of all pending [OfflineOperation]s for this type.
     194           1 :   Set<OfflineOperation<T>> get offlineOperations =>
     195           2 :       remoteAdapter.offlineOperations;
     196             : 
     197             :   // watchers
     198             : 
     199             :   /// Watches a provider wrapping [Repository.watchAllNotifier]
     200             :   /// which allows the watcher to be notified of changes
     201             :   /// on any model of this [type].
     202             :   ///
     203             :   /// Example: Watch all models of type `books` on a Riverpod hook-enabled app.
     204             :   ///
     205             :   /// ```
     206             :   /// ref.books.watchAll();
     207             :   /// ```
     208           1 :   DataState<List<T>?> watchAll({
     209             :     bool? remote,
     210             :     Map<String, dynamic>? params,
     211             :     Map<String, String>? headers,
     212             :     bool? syncLocal,
     213             :     String? finder,
     214             :     DataRequestLabel? label,
     215             :   }) {
     216           1 :     final provider = watchAllProvider(
     217             :       remote: remote,
     218             :       params: params,
     219             :       headers: headers,
     220             :       syncLocal: syncLocal,
     221             :       finder: finder,
     222             :       label: label,
     223             :     );
     224           3 :     return remoteAdapter.internalWatch!(provider);
     225             :   }
     226             : 
     227             :   /// Watches a provider wrapping [Repository.watchOneNotifier]
     228             :   /// which allows the watcher to be notified of changes
     229             :   /// on a specific model of this [type], optionally reacting
     230             :   /// to selected relationships of this model via [alsoWatch].
     231             :   ///
     232             :   /// Example: Watch model of type `books` and `id=1` along
     233             :   /// with its `author` relationship on a Riverpod hook-enabled app.
     234             :   ///
     235             :   /// ```
     236             :   /// ref.books.watchOne(1, alsoWatch: (book) => [book.author]);
     237             :   /// ```
     238           1 :   DataState<T?> watchOne(
     239             :     Object model, {
     240             :     bool? remote,
     241             :     Map<String, dynamic>? params,
     242             :     Map<String, String>? headers,
     243             :     AlsoWatch<T>? alsoWatch,
     244             :     String? finder,
     245             :     DataRequestLabel? label,
     246             :   }) {
     247           1 :     final provider = watchOneProvider(
     248             :       model,
     249             :       remote: remote,
     250             :       params: params,
     251             :       headers: headers,
     252             :       alsoWatch: alsoWatch,
     253             :       finder: finder,
     254             :       label: label,
     255             :     );
     256           3 :     return remoteAdapter.internalWatch!(provider);
     257             :   }
     258             : 
     259             :   // providers
     260             : 
     261           1 :   AutoDisposeStateNotifierProvider<DataStateNotifier<List<T>?>,
     262             :       DataState<List<T>?>> watchAllProvider({
     263             :     bool? remote,
     264             :     Map<String, dynamic>? params,
     265             :     Map<String, String>? headers,
     266             :     bool? syncLocal,
     267             :     String? finder,
     268             :     DataRequestLabel? label,
     269             :   }) {
     270           2 :     return _watchAllProvider(
     271           1 :       WatchArgs(
     272             :         remote: remote,
     273             :         params: params,
     274             :         headers: headers,
     275             :         syncLocal: syncLocal,
     276             :         finder: finder,
     277             :         label: label,
     278             :       ),
     279             :     );
     280             :   }
     281             : 
     282           1 :   late final _watchAllProvider = StateNotifierProvider.autoDispose
     283           2 :       .family<DataStateNotifier<List<T>?>, DataState<List<T>?>, WatchArgs<T>>(
     284           1 :           (ref, args) {
     285           2 :     return remoteAdapter.watchAllNotifier(
     286           1 :       remote: args.remote,
     287           1 :       params: args.params,
     288           1 :       headers: args.headers,
     289           1 :       syncLocal: args.syncLocal,
     290           1 :       finder: args.finder,
     291           1 :       label: args.label,
     292             :     );
     293             :   });
     294             : 
     295           1 :   AutoDisposeStateNotifierProvider<DataStateNotifier<T?>, DataState<T?>>
     296             :       watchOneProvider(
     297             :     Object model, {
     298             :     bool? remote,
     299             :     Map<String, dynamic>? params,
     300             :     Map<String, String>? headers,
     301             :     AlsoWatch<T>? alsoWatch,
     302             :     String? finder,
     303             :     DataRequestLabel? label,
     304             :   }) {
     305           2 :     final key = remoteAdapter.keyForModelOrId(model);
     306           2 :     return _watchOneProvider(
     307           1 :       WatchArgs(
     308             :         key: key,
     309             :         remote: remote,
     310             :         params: params,
     311             :         headers: headers,
     312             :         alsoWatch: alsoWatch,
     313             :         finder: finder,
     314             :         label: label,
     315             :       ),
     316             :     );
     317             :   }
     318             : 
     319           1 :   late final _watchOneProvider = StateNotifierProvider.autoDispose
     320           3 :       .family<DataStateNotifier<T?>, DataState<T?>, WatchArgs<T>>((ref, args) {
     321           2 :     return remoteAdapter.watchOneNotifier(
     322           1 :       args.key!,
     323           1 :       remote: args.remote,
     324           1 :       params: args.params,
     325           1 :       headers: args.headers,
     326           1 :       alsoWatch: args.alsoWatch,
     327           1 :       finder: args.finder,
     328           1 :       label: args.label,
     329             :     );
     330             :   });
     331             : 
     332             :   // notifiers
     333             : 
     334           1 :   DataStateNotifier<List<T>?> watchAllNotifier(
     335             :       {bool? remote,
     336             :       Map<String, dynamic>? params,
     337             :       Map<String, String>? headers,
     338             :       bool? syncLocal,
     339             :       String? finder,
     340             :       DataRequestLabel? label}) {
     341           1 :     final provider = watchAllProvider(
     342             :       remote: remote,
     343             :       params: params,
     344             :       headers: headers,
     345             :       syncLocal: syncLocal,
     346             :       finder: finder,
     347             :       label: label,
     348             :     );
     349           4 :     return remoteAdapter.internalWatch!(provider.notifier);
     350             :   }
     351             : 
     352           1 :   DataStateNotifier<T?> watchOneNotifier(Object model,
     353             :       {bool? remote,
     354             :       Map<String, dynamic>? params,
     355             :       Map<String, String>? headers,
     356             :       AlsoWatch<T>? alsoWatch,
     357             :       String? finder,
     358             :       DataRequestLabel? label}) {
     359           4 :     return remoteAdapter.internalWatch!(watchOneProvider(
     360             :       model,
     361             :       remote: remote,
     362             :       params: params,
     363             :       headers: headers,
     364             :       alsoWatch: alsoWatch,
     365             :       finder: finder,
     366             :       label: label,
     367           1 :     ).notifier);
     368             :   }
     369             : 
     370             :   /// Watch this model
     371           1 :   T watch(T model) {
     372           2 :     return watchOne(model, remote: false).model!;
     373             :   }
     374             : 
     375           0 :   bool get verbose => remoteAdapter._verbose;
     376           1 :   set verbose(bool value) {
     377           2 :     remoteAdapter._verbose = value;
     378             :   }
     379             : }
     380             : 
     381             : /// Annotation on a [DataModel] model to request a [Repository] be generated for it.
     382             : ///
     383             : /// Takes a list of [adapters] to be mixed into this [Repository].
     384             : /// Public methods of these [adapters] mixins will be made available in the repository
     385             : /// via extensions.
     386             : ///
     387             : /// A classic example is:
     388             : ///
     389             : /// ```
     390             : /// @JsonSerializable()
     391             : /// @DataRepository([JSONAPIAdapter])
     392             : /// class Todo with DataModel<Todo> {
     393             : ///   @override
     394             : ///   final int id;
     395             : ///   final String title;
     396             : ///   final bool completed;
     397             : ///
     398             : ///   Todo({this.id, this.title, this.completed = false});
     399             : /// }
     400             : ///```
     401             : class DataRepository {
     402             :   final List<Type> adapters;
     403             :   final bool remote;
     404             :   final bool verbose;
     405           6 :   const DataRepository(this.adapters,
     406             :       {this.remote = true, this.verbose = false});
     407             : }

Generated by: LCOV version 1.15