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