Flutter Data
Persistent reactive models in Flutter with zero boilerplate
Flutter Data is an offline-first data framework with a customizable REST client and powerful model relationships.
Inspired by Ember Data and ActiveRecord.
Features
- Repositories for all models 🚀
- CRUD and custom remote endpoints
- StateNotifier watcher APIs
- Built for offline-first 🔌
- Hive-based local storage at its core
- Failure handling & retry API
- Intuitive APIs, effortless setup 💙
- Truly configurable and composable via Dart mixins and codegen
- Built-in Riverpod providers for all models
- Exceptional relationship support ⚡️
- Automatically synchronized, fully traversable relationship graph
- Reactive relationships
Check out the Tutorial 📚 where we build a TO-DO app from the ground up in record time.
👩🏾💻 Usage
(See the quickstart guide for setup and boot configuration.)
For a given User
model annotated with @DataRepository
:
@JsonSerializable()
@DataRepository([MyJSONServerAdapter])
class User extends DataModel<User> {
@override
final int? id; // ID can be of any type
final String name;
User({this.id, required this.name});
// `User.fromJson` and `toJson` optional
}
mixin MyJSONServerAdapter on RemoteAdapter<User> {
@override
String get baseUrl => "https://my-json-server.typicode.com/flutterdata/demo/";
}
After a code-gen build, Flutter Data will generate a Repository<User>
(and shortcuts like ref.users
for Riverpod):
@override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.users.watchOne(1);
if (state.isLoading) {
return Center(child: const CircularProgressIndicator());
}
final user = state.model;
return Text(user.name);
}
Update the user:
TextButton(
onPressed: () => ref.users.save(User(id: 1, name: 'Updated')),
child: Text('Update'),
),
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!
state
is of type DataState
which has loading, error and data substates.
In addition to the reactivity, DataModel
s get extensions and automatic relationships, ActiveRecord-style, so the above becomes:
GestureDetector(
onTap: () => User(id: 1, name: 'Updated').save(),
child: Text('Update')
),
More examples:
final task = await Task(title: 'Finish docs').save();
// or its equivalent:
final task = await ref.tasks.save(Task(title: 'Finish docs'));
// POST https://my-json-server.typicode.com/flutterdata/demo/tasks/
print(task.id); // 201
final user = await repository.findOne(1, params: {'_embed': 'tasks'});
// (remember repository can be accessed via ref.users)
// GET https://my-json-server.typicode.com/flutterdata/demo/users/1?_embed=tasks
print(user.tasks.length); // 20
await user.tasks.last.delete();
Explore the Documentation.
Compatibility
Fully compatible with the tools we know and love:
Flutter | ✅ | And pure Dart, too. |
Flutter Web | ✅ | Supported (untested) |
json_serializable | ✅ | Fully supported (but not required) |
Riverpod | ✅ | Supported & automatically wired up |
Classic JSON REST API | ✅ | Built-in support! |
JSON:API | ✅ | Supported via external adapter |
Firebase, Supabase, GraphQL | ✅ | Can be fully supported by writing custom adapters |
Freezed | ✅ | Supported! |
📲 Apps using Flutter Data in production
➕ Questions and collaborating
Please use Github to ask questions, open issues and send PRs. Thanks!
Tests can be run with: dart test
📝 License
See LICENSE.