Flutter Data

tests codecov pub.dev license

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 🚀
  • 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, DataModels 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

logos

➕ 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.