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 0 : @nonVirtual
20 0 : 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,
27 : bool? verbose,
28 : required Map<String, RemoteAdapter> adapters}) async {
29 1 : if (isInitialized) return this;
30 2 : _adapters.addAll(adapters);
31 3 : await remoteAdapter.initialize(
32 : remote: remote,
33 : verbose: verbose,
34 : adapters: adapters,
35 1 : read: _read,
36 : );
37 1 : _isInit = true;
38 : return this;
39 : }
40 :
41 : /// Returns whether this [Repository] is initialized
42 : /// (when its underlying [RemoteAdapter] is).
43 1 : @override
44 3 : bool get isInitialized => _isInit && remoteAdapter.isInitialized;
45 :
46 : /// Disposes this [Repository] and everything that depends on it.
47 1 : @override
48 : void dispose() {
49 1 : if (isInitialized) {
50 2 : remoteAdapter.dispose();
51 1 : _isInit = false;
52 : }
53 : }
54 :
55 : // Public API
56 :
57 : /// Returns all models of type [T].
58 : ///
59 : /// If [_RemoteAdapter.shouldLoadRemoteAll] (function of [remote]) is `true`,
60 : /// it will initiate an HTTP call.
61 : /// Otherwise returns all models of type [T] in local storage.
62 : ///
63 : /// Arguments [params] and [headers] will be merged with
64 : /// [_RemoteAdapter.defaultParams] and [_RemoteAdapter.defaultHeaders], respectively.
65 : ///
66 : /// For local storage of type [T] to be synchronized to the exact resources
67 : /// returned from the remote source when using `findAll`, pass `syncLocal: true`.
68 : /// This call would, for example, reflect server-side resource deletions.
69 : /// The default is `syncLocal: false`.
70 : ///
71 : /// See also: [_RemoteAdapter.urlForFindAll], [_RemoteAdapter.methodForFindAll].
72 1 : Future<List<T>> findAll({
73 : bool? remote,
74 : Map<String, dynamic>? params,
75 : Map<String, String>? headers,
76 : bool? syncLocal,
77 : OnDataError<List<T>>? onError,
78 : }) {
79 2 : return remoteAdapter.findAll(
80 : remote: remote,
81 : params: params,
82 : headers: headers,
83 : syncLocal: syncLocal,
84 : onError: onError,
85 : );
86 : }
87 :
88 : /// Returns model of type [T] by [id].
89 : ///
90 : /// If [_RemoteAdapter.shouldLoadRemoteOne] (function of [remote]) is `true`,
91 : /// it will initiate an HTTP call.
92 : /// Otherwise returns model of type [T] and [id] in local storage.
93 : ///
94 : /// Arguments [params] and [headers] will be merged with
95 : /// [_RemoteAdapter.defaultParams] and [_RemoteAdapter.defaultHeaders], respectively.
96 : ///
97 : /// See also: [_RemoteAdapter.urlForFindOne], [_RemoteAdapter.methodForFindOne].
98 1 : Future<T?> findOne(
99 : final dynamic id, {
100 : bool? remote,
101 : Map<String, dynamic>? params,
102 : Map<String, String>? headers,
103 : OnDataError<T>? onError,
104 : }) {
105 2 : return remoteAdapter.findOne(
106 : id,
107 : remote: remote,
108 : params: params,
109 : headers: headers,
110 : onError: onError,
111 : );
112 : }
113 :
114 : /// Saves [model] of type [T].
115 : ///
116 : /// If [remote] is `true`, it will initiate an HTTP call.
117 : ///
118 : /// Always persists to local storage.
119 : ///
120 : /// Arguments [params] and [headers] will be merged with
121 : /// [_RemoteAdapter.defaultParams] and [_RemoteAdapter.defaultHeaders], respectively.
122 : ///
123 : /// See also: [_RemoteAdapter.urlForSave], [_RemoteAdapter.methodForSave].
124 1 : Future<T> save(
125 : T model, {
126 : bool? remote,
127 : Map<String, dynamic>? params,
128 : Map<String, String>? headers,
129 : OnData<T>? onSuccess,
130 : OnDataError<T>? onError,
131 : }) {
132 2 : return remoteAdapter.save(
133 : model,
134 : remote: remote,
135 : params: params,
136 : headers: headers,
137 : onSuccess: onSuccess,
138 : onError: onError,
139 : );
140 : }
141 :
142 : /// Deletes [model] of type [T].
143 : ///
144 : /// If [remote] is `true`, it will initiate an HTTP call.
145 : ///
146 : /// Always deletes from local storage.
147 : ///
148 : /// Arguments [params] and [headers] will be merged with
149 : /// [_RemoteAdapter.defaultParams] and [_RemoteAdapter.defaultHeaders], respectively.
150 : ///
151 : /// See also: [_RemoteAdapter.urlForDelete], [_RemoteAdapter.methodForDelete].
152 1 : Future<void> delete(
153 : dynamic model, {
154 : bool? remote,
155 : Map<String, dynamic>? params,
156 : Map<String, String>? headers,
157 : OnData<void>? onSuccess,
158 : OnDataError<void>? onError,
159 : }) {
160 2 : return remoteAdapter.delete(
161 : model,
162 : remote: remote,
163 : params: params,
164 : headers: headers,
165 : onSuccess: onSuccess,
166 : onError: onError,
167 : );
168 : }
169 :
170 : /// Deletes all models of type [T] in local storage.
171 : ///
172 : ///
173 : ///
174 : /// If you need to clear all models, use the
175 : /// `repositoryProviders` map exposed on your `main.data.dart`.
176 3 : Future<void> clear() => remoteAdapter.clear();
177 :
178 : // offline
179 :
180 : /// Gets a list of all pending [OfflineOperation]s for this type.
181 1 : Set<OfflineOperation<T>> get offlineOperations =>
182 2 : remoteAdapter.offlineOperations;
183 :
184 : // watchers
185 :
186 : /// Watches changes on all models of type [T] in local storage.
187 : ///
188 : /// When called, will in turn call [findAll] with [remote], [params],
189 : /// [headers], [syncLocal].
190 1 : DataStateNotifier<List<T>> watchAllNotifier({
191 : bool? remote,
192 : Map<String, dynamic>? params,
193 : Map<String, String>? headers,
194 : bool? syncLocal,
195 : }) {
196 2 : return remoteAdapter.watchAllNotifier(
197 : remote: remote,
198 : params: params,
199 : headers: headers,
200 : syncLocal: syncLocal,
201 : );
202 : }
203 :
204 : /// Watches a provider wrapping [watchAllNotifier]
205 : /// which allows the watcher to be notified of changes
206 : /// on any model of this [type].
207 : ///
208 : /// Example: Watch all models of type `books` on a Riverpod hook-enabled app.
209 : ///
210 : /// ```
211 : /// ref.books.watchAll();
212 : /// ```
213 0 : DataState<List<T>> watchAll({
214 : bool? remote,
215 : Map<String, dynamic>? params,
216 : Map<String, String>? headers,
217 : bool? syncLocal,
218 : }) {
219 0 : return remoteAdapter.watchAll(
220 : remote: remote,
221 : params: params,
222 : headers: headers,
223 : syncLocal: syncLocal,
224 : );
225 : }
226 :
227 : /// Returns a [DataState] notifier with changes on model of
228 : /// type [T] by [id] in local storage.
229 : ///
230 : /// Optionally reacts to selected relationships of this model via [alsoWatch].
231 : ///
232 : /// Will invoke [findAll] with [remote], [params], [headers].
233 1 : DataStateNotifier<T?> watchOneNotifier(
234 : dynamic id, {
235 : bool? remote,
236 : Map<String, dynamic>? params,
237 : Map<String, String>? headers,
238 : AlsoWatch<T>? alsoWatch,
239 : }) {
240 2 : return remoteAdapter.watchOneNotifier(
241 : id,
242 : remote: remote,
243 : params: params,
244 : headers: headers,
245 : alsoWatch: alsoWatch,
246 : );
247 : }
248 :
249 : /// Watches a provider wrapping [watchOneNotifier]
250 : /// which allows the watcher to be notified of changes
251 : /// on a specific model of this [type], optionally reacting
252 : /// to selected relationships of this model via [alsoWatch].
253 : ///
254 : /// Example: Watch model of type `books` and `id=1` along
255 : /// with its `author` relationship on a Riverpod hook-enabled app.
256 : ///
257 : /// ```
258 : /// ref.books.watchOne(1, alsoWatch: (book) => [book.author]);
259 : /// ```
260 0 : DataState<T?> watchOne(
261 : dynamic id, {
262 : bool? remote,
263 : Map<String, dynamic>? params,
264 : Map<String, String>? headers,
265 : AlsoWatch<T>? alsoWatch,
266 : }) {
267 0 : return remoteAdapter.watchOne(
268 : id,
269 : remote: remote,
270 : params: params,
271 : headers: headers,
272 : alsoWatch: alsoWatch,
273 : );
274 : }
275 : }
276 :
277 : /// Annotation on a [DataModel] model to request a [Repository] be generated for it.
278 : ///
279 : /// Takes a list of [adapters] to be mixed into this [Repository].
280 : /// Public methods of these [adapters] mixins will be made available in the repository
281 : /// via extensions.
282 : ///
283 : /// A classic example is:
284 : ///
285 : /// ```
286 : /// @JsonSerializable()
287 : /// @DataRepository([JSONAPIAdapter])
288 : /// class Todo with DataModel<Todo> {
289 : /// @override
290 : /// final int id;
291 : /// final String title;
292 : /// final bool completed;
293 : ///
294 : /// Todo({this.id, this.title, this.completed = false});
295 : /// }
296 : ///```
297 : class DataRepository {
298 : final List<Type> adapters;
299 : final bool remote;
300 6 : const DataRepository(this.adapters, {this.remote = true});
301 : }
|