99
1010## Persistent reactive models in Flutter with zero boilerplate
1111
12- Flutter Data is an offline-first data framework with a customizable REST client and powerful model relationships.
12+ Flutter Data is an offline-first data framework with a customizable REST client and powerful model relationships, built on Riverpod .
1313
1414<small >Inspired by [ Ember Data] ( https://github.com/emberjs/data ) and [ ActiveRecord] ( https://guides.rubyonrails.org/active_record_basics.html ) .</small >
1515
1616## Features
1717
18- - ** Repositories for all models** 🚀
19- - CRUD and custom remote endpoints
18+ - ** Adapters for all models** 🚀
19+ - Default CRUD and custom remote endpoints
2020 - [ StateNotifier] ( https://pub.dev/packages/state_notifier ) watcher APIs
2121- ** Built for offline-first** 🔌
22- - [ Hive ] ( https://docs.hivedb. dev/ ) -based local storage at its core
22+ - [ SQLite3 ] ( https://pub. dev/packages/sqlite3 ) -based local storage at its core, with adapters for many other engines: Objectbox, Isar, etc (coming soon)
2323 - Failure handling & retry API
2424- ** Intuitive APIs, effortless setup** 💙
2525 - Truly configurable and composable via Dart mixins and codegen
@@ -28,17 +28,15 @@ Flutter Data is an offline-first data framework with a customizable REST client
2828 - Automatically synchronized, fully traversable relationship graph
2929 - Reactive relationships
3030
31- ** Check out the [ Tutorial ] ( https://flutterdata.dev/tutorial ) 📚 where we build a TO-DO app from the ground up in record time. **
31+ ## 👩🏾💻 Quick introduction
3232
33- ## 👩🏾💻 Usage
33+ In Flutter Data, every model gets is adapter. These adapters can be extended by mixing in custom adapters.
3434
35- (See the [ quickstart guide] ( https://flutterdata.dev/docs/quickstart/ ) for setup and boot configuration.)
36-
37- For a given ` User ` model annotated with ` @DataRepository ` :
35+ Annotate a model with ` @DataAdapter ` and pass a custom adapter:
3836
3937``` dart
4038@JsonSerializable()
41- @DataRepository ([MyJSONServerAdapter])
39+ @DataAdapter ([MyJSONServerAdapter])
4240class User extends DataModel<User> {
4341 @override
4442 final int? id; // ID can be of any type
@@ -53,7 +51,7 @@ mixin MyJSONServerAdapter on RemoteAdapter<User> {
5351}
5452```
5553
56- After a code-gen build , Flutter Data will generate a ` Repository <User>` (and shortcuts like ` ref.users ` for Riverpod):
54+ After code-gen, Flutter Data will generate the resulting ` Adapter <User>` which is accessible via Riverpod's ` ref.users ` or ` container.users ` .
5755
5856``` dart
5957@override
@@ -67,7 +65,7 @@ Widget build(BuildContext context, WidgetRef ref) {
6765}
6866```
6967
70- Update the user:
68+ To update the user:
7169
7270``` dart
7371TextButton(
@@ -76,11 +74,11 @@ TextButton(
7674),
7775```
7876
79- ` ref.users.watchOne(1) ` will make a background HTTP request (to ` https://my-json-server.typicode.com/flutterdata/demo/users/1 ` in this case), deserialize data and listen for any further changes to the ` User ` – whether those are local or remote!
77+ ` ref.users.watchOne(1) ` will make a background HTTP request (to ` https://my-json-server.typicode.com/flutterdata/demo/users/1 ` in this case), deserialize data and listen for any further local changes to the user.
8078
8179` state ` is of type ` DataState ` which has loading, error and data substates.
8280
83- In addition to the reactivity, ` DataModel ` s get extensions and automatic relationships, ActiveRecord-style, so the above becomes:
81+ In addition to the reactivity, models have ActiveRecord-style extension methods so the above becomes:
8482
8583``` dart
8684GestureDetector(
@@ -89,25 +87,6 @@ GestureDetector(
8987),
9088```
9189
92- More examples:
93-
94- ``` dart
95- final task = await Task(title: 'Finish docs').save();
96- // or its equivalent:
97- final task = await ref.tasks.save(Task(title: 'Finish docs'));
98- // POST https://my-json-server.typicode.com/flutterdata/demo/tasks/
99- print(task.id); // 201
100-
101- final user = await repository.findOne(1, params: {'_embed': 'tasks'});
102- // (remember repository can be accessed via ref.users)
103- // GET https://my-json-server.typicode.com/flutterdata/demo/users/1?_embed=tasks
104- print(user.tasks.length); // 20
105-
106- await user.tasks.last.delete();
107- ```
108-
109- ** Explore the [ Documentation] ( https://flutterdata.dev/docs/ ) .**
110-
11190## Compatibility
11291
11392Fully compatible with the tools we know and love:
@@ -124,12 +103,7 @@ Fully compatible with the tools we know and love:
124103 <tr>
125104 <td class="font-bold px-4 py-2"><strong>Flutter</strong></td>
126105 <td class="px-4 py-2">✅</td>
127- <td class="px-4 py-2 text-sm">And pure Dart, too.</td>
128- </tr>
129- <tr class="bg-yellow-50">
130- <td class="font-bold px-4 py-2"><strong>Flutter Web</strong></td>
131- <td class="px-4 py-2">✅</td>
132- <td class="px-4 py-2 text-sm">Supported (untested)</td>
106+ <td class="px-4 py-2 text-sm">Or plain Dart. It does not require Flutter.</td>
133107 </tr>
134108 <tr>
135109 <td class="font-bold px-4 py-2"><strong>json_serializable</strong></td>
@@ -155,13 +129,18 @@ Fully compatible with the tools we know and love:
155129 <tr class="bg-yellow-50">
156130 <td class="font-bold px-4 py-2"><strong>Firebase, Supabase, GraphQL</strong></td>
157131 <td class="px-4 py-2">✅</td>
158- <td class="px-4 py-2 text-sm">Can be fully supported by writing <a href="https://flutterdata.dev/docs/adapters/"> custom adapters</a></td>
132+ <td class="px-4 py-2 text-sm">Can be fully supported by writing custom adapters</a></td>
159133 </tr>
160134 <tr>
161135 <td class="font-bold px-4 py-2"><strong>Freezed</strong></td>
162136 <td class="px-4 py-2">✅</td>
163137 <td class="px-4 py-2 text-sm">Supported!</td>
164138 </tr>
139+ <tr class="bg-yellow-50">
140+ <td class="font-bold px-4 py-2"><strong>Flutter Web</strong></td>
141+ <td class="px-4 py-2">✅</td>
142+ <td class="px-4 py-2 text-sm">TBD</td>
143+ </tr>
165144 </tbody >
166145</table >
167146
@@ -171,6 +150,234 @@ Fully compatible with the tools we know and love:
171150
172151 - [ Drexbible] ( https://snapcraft.io/drexbible )
173152
153+ ## 📚 API
154+
155+ ### Adapters
156+
157+ WIP. Method names should be self explanatory. All of these methods have a reasonable default implementation.
158+
159+ #### Public API
160+
161+ ``` dart
162+ // local storage
163+
164+ List<T> findAllLocal();
165+
166+ List<T> findManyLocal(Iterable<String> keys);
167+
168+ List<T> deserializeFromResult(ResultSet result);
169+
170+ T? findOneLocal(String? key);
171+
172+ T? findOneLocalById(Object id);
173+
174+ bool exists(String key);
175+
176+ T saveLocal(T model, {bool notify = true});
177+
178+ Future<List<String>?> saveManyLocal(Iterable<DataModelMixin> models,
179+ {bool notify = true, bool async = true});
180+
181+ void deleteLocal(T model, {bool notify = true});
182+
183+ void deleteLocalById(Object id, {bool notify = true});
184+
185+ void deleteLocalByKeys(Iterable<String> keys, {bool notify = true});
186+
187+ Future<void> clearLocal({bool notify = true});
188+
189+ int get countLocal;
190+
191+ Set<String> get keys;
192+
193+ // remote
194+
195+ Future<List<T>> findAll({
196+ bool remote = true,
197+ bool background = false,
198+ Map<String, dynamic>? params,
199+ Map<String, String>? headers,
200+ bool syncLocal = false,
201+ OnSuccessAll<T>? onSuccess,
202+ OnErrorAll<T>? onError,
203+ DataRequestLabel? label,
204+ });
205+
206+ Future<T?> findOne(
207+ Object id, {
208+ bool remote = true,
209+ bool? background,
210+ Map<String, dynamic>? params,
211+ Map<String, String>? headers,
212+ OnSuccessOne<T>? onSuccess,
213+ OnErrorOne<T>? onError,
214+ DataRequestLabel? label,
215+ });
216+
217+ Future<T> save(
218+ T model, {
219+ bool remote = true,
220+ Map<String, dynamic>? params,
221+ Map<String, String>? headers,
222+ OnSuccessOne<T>? onSuccess,
223+ OnErrorOne<T>? onError,
224+ DataRequestLabel? label,
225+ });
226+
227+ Future<T?> delete(
228+ Object model, {
229+ bool remote = true,
230+ Map<String, dynamic>? params,
231+ Map<String, String>? headers,
232+ OnSuccessOne<T>? onSuccess,
233+ OnErrorOne<T>? onError,
234+ DataRequestLabel? label,
235+ });
236+
237+ Set<OfflineOperation<T>> get offlineOperations;
238+
239+ // serialization
240+
241+ Map<String, dynamic> serializeLocal(T model, {bool withRelationships = true});
242+
243+ T deserializeLocal(Map<String, dynamic> map, {String? key});
244+
245+ Future<Map<String, dynamic>> serialize(T model,
246+ {bool withRelationships = true});
247+
248+ Future<DeserializedData<T>> deserialize(Object? data,
249+ {String? key, bool async = true});
250+
251+ Future<DeserializedData<T>> deserializeAndSave(Object? data,
252+ {String? key, bool notify = true, bool ignoreReturn = false});
253+
254+ // watchers
255+
256+ DataState<List<T>> watchAll({
257+ bool remote = false,
258+ Map<String, dynamic>? params,
259+ Map<String, String>? headers,
260+ bool syncLocal = false,
261+ String? finder,
262+ DataRequestLabel? label,
263+ });
264+
265+ DataState<T?> watchOne(
266+ Object model, {
267+ bool remote = false,
268+ Map<String, dynamic>? params,
269+ Map<String, String>? headers,
270+ AlsoWatch<T>? alsoWatch,
271+ String? finder,
272+ DataRequestLabel? label,
273+ });
274+
275+ DataStateNotifier<List<T>> watchAllNotifier(
276+ {bool remote = false,
277+ Map<String, dynamic>? params,
278+ Map<String, String>? headers,
279+ bool syncLocal = false,
280+ String? finder,
281+ DataRequestLabel? label});
282+
283+ DataStateNotifier<T?> watchOneNotifier(Object model,
284+ {bool remote = false,
285+ Map<String, dynamic>? params,
286+ Map<String, String>? headers,
287+ AlsoWatch<T>? alsoWatch,
288+ String? finder,
289+ DataRequestLabel? label});
290+
291+ final coreNotifierThrottleDurationProvider;
292+ ```
293+
294+ #### Protected API
295+
296+ ``` dart
297+ // adapter
298+
299+ Future<void> onInitialized();
300+
301+ Future<Adapter<T>> initialize({required Ref ref});
302+
303+ void dispose();
304+
305+ Future<R> runInIsolate<R>(FutureOr<R> fn(Adapter adapter));
306+
307+ void log(DataRequestLabel label, String message, {int logLevel = 1});
308+
309+ void onModelInitialized(T model) {};
310+
311+ // remote
312+
313+ String get baseUrl;
314+
315+ FutureOr<Map<String, dynamic>> get defaultParams;
316+
317+ FutureOr<Map<String, String>> get defaultHeaders;
318+
319+ String urlForFindAll(Map<String, dynamic> params);
320+
321+ DataRequestMethod methodForFindAll(Map<String, dynamic> params);
322+
323+ String urlForFindOne(id, Map<String, dynamic> params);
324+
325+ DataRequestMethod methodForFindOne(id, Map<String, dynamic> params);
326+
327+ String urlForSave(id, Map<String, dynamic> params);
328+
329+ DataRequestMethod methodForSave(id, Map<String, dynamic> params);
330+
331+ String urlForDelete(id, Map<String, dynamic> params);
332+
333+ DataRequestMethod methodForDelete(id, Map<String, dynamic> params);
334+
335+ bool shouldLoadRemoteAll(
336+ bool remote,
337+ Map<String, dynamic> params,
338+ Map<String, String> headers,
339+ );
340+
341+ bool shouldLoadRemoteOne(
342+ Object? id,
343+ bool remote,
344+ Map<String, dynamic> params,
345+ Map<String, String> headers,
346+ );
347+
348+ bool isOfflineError(Object? error);
349+
350+ http.Client get httpClient;
351+
352+ Future<R?> sendRequest<R>(
353+ final Uri uri, {
354+ DataRequestMethod method = DataRequestMethod.GET,
355+ Map<String, String>? headers,
356+ Object? body,
357+ _OnSuccessGeneric<R>? onSuccess,
358+ _OnErrorGeneric<R>? onError,
359+ bool omitDefaultParams = false,
360+ bool returnBytes = false,
361+ DataRequestLabel? label,
362+ bool closeClientAfterRequest = true,
363+ });
364+
365+ FutureOr<R?> onSuccess<R>(
366+ DataResponse response, DataRequestLabel label);
367+
368+ FutureOr<R?> onError<R>(
369+ DataException e,
370+ DataRequestLabel? label,
371+ );
372+
373+ // serialization
374+
375+ Map<String, dynamic> transformSerialize(Map<String, dynamic> map,
376+ {bool withRelationships = true});
377+
378+ Map<String, dynamic> transformDeserialize(Map<String, dynamic> map);
379+ ```
380+
174381## ➕ Questions and collaborating
175382
176383Please use Github to ask questions, open issues and send PRs. Thanks!
0 commit comments