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 ProviderReference _ref;
6 1 : Repository(this._ref);
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 : ref: _ref,
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 ?? true,
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 : ///
191 : /// If [syncLocal] is set to `false` but results still need to be filtered,
192 : /// use [filterLocal]. All data updates will be filtered through it.
193 1 : DataStateNotifier<List<T>> watchAll({
194 : bool? remote,
195 : Map<String, dynamic>? params,
196 : Map<String, String>? headers,
197 : bool? syncLocal,
198 : bool Function(T)? filterLocal,
199 : }) {
200 2 : return remoteAdapter.watchAll(
201 : remote: remote!,
202 : params: params,
203 : headers: headers,
204 : filterLocal: filterLocal,
205 : syncLocal: syncLocal,
206 : );
207 : }
208 :
209 : /// Watches changes on model of type [T] by [id] in local storage.
210 : ///
211 : /// Optionally [alsoWatch]es selected relationships of this model.
212 : ///
213 : /// Example: Watch `Book` with `id=1` and its `Author` relationship.
214 : ///
215 : /// ```
216 : /// bookRepository.watchOne('1', alsoWatch: (book) => [book.author]);
217 : /// ```
218 : ///
219 : /// When called, will in turn call [findAll] with [remote], [params], [headers].
220 1 : DataStateNotifier<T?> watchOne(
221 : dynamic id, {
222 : bool? remote,
223 : Map<String, dynamic>? params,
224 : Map<String, String>? headers,
225 : AlsoWatch<T>? alsoWatch,
226 : }) {
227 2 : return remoteAdapter.watchOne(
228 : id,
229 : remote: remote,
230 : params: params,
231 : headers: headers,
232 : alsoWatch: alsoWatch,
233 : );
234 : }
235 : }
236 :
237 : /// Annotation on a [DataModel] model to request a [Repository] be generated for it.
238 : ///
239 : /// Takes a list of [adapters] to be mixed into this [Repository].
240 : /// Public methods of these [adapters] mixins will be made available in the repository
241 : /// via extensions.
242 : ///
243 : /// A classic example is:
244 : ///
245 : /// ```
246 : /// @JsonSerializable()
247 : /// @DataRepository([JSONAPIAdapter])
248 : /// class Todo with DataModel<Todo> {
249 : /// @override
250 : /// final int id;
251 : /// final String title;
252 : /// final bool completed;
253 : ///
254 : /// Todo({this.id, this.title, this.completed = false});
255 : /// }
256 : ///```
257 : class DataRepository {
258 : final List<Type> adapters;
259 : final bool remote;
260 6 : const DataRepository(this.adapters, {this.remote = true});
261 : }
|