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 : }
|