Flutter Data features an advanced relationship mapping system.


  • HasMany<T> for to-many relationships
  • BelongsTo<T> for to-one relationships

As an example, a User has many Tasks:

class User with DataModel<User> {
  final int? id;
  final String name;
  final HasMany<Task>? tasks;

  User({, required, this.tasks});

and a Task belongs to a User:

class Task with DataModel<Task> {
  final int? id;
  final String title;
  final bool completed;
  final BelongsTo<User>? user;

  Task({, required this.title, this.completed = false, this.user});

So long as:

  • Models have all their relationships initialized
  • The API responds correctly with relationship data (for example a User resource with a collection of Task models – or just IDs if models are already present in local storage)

we can expect the following to work:

final user = await repository.findOne(1, params: {'_embed': 'tasks'});
final task = user!.tasks!.first;

print(task.title); // write Flutter Data docs
print(task.user!.value!.name); // Frank

// or

final house = House(address: "Sakharova Prospekt, 19");
final family = Family(surname: 'Kamchatka', house: BelongsTo(house));

print(;  // Kamchatka

We can infinitely navigate the relationship graph as it’s based on a reactive graph data structure (GraphNotifier).


For relationships to work, they must (a) be different than null and (b) initialized.

final task = Task(title: 'do 1', user: BelongsTo()).init(;

By initializing the new Task model, all its relationships will be initialized as well.

If we don’t want to supply a new relationship object like above, we may provide defaults like so:

class Task with DataModel<Task> {
  final int? id;
  final String title;
  final bool completed;
  late final BelongsTo<User> user;

  Task({, required this.title, this.completed = false, BelongsTo<User>? user}) :
    user = user ?? BelongsTo();


Inverse relationships are guessed when unambiguous (one relationship of inverse type).

Not in this case, as Family has two BelongsTo<House>s:

class Family with DataModel<Family> {
  final String? id;
  final BelongsTo<House>? cottage;
  final BelongsTo<House>? residence;


class House with DataModel<House> {
  final String? id;
  final BelongsTo<Family>? owner;

    BelongsTo<Family>? owner,
  }) : owner = owner ?? BelongsTo();

If you wish to disambuiguate or to be explicit, annotate your relationship in the House model:

@DataRelationship(inverse: 'residence')
final BelongsTo<Family>? owner;

Here’s another example, a tree structure using custom inverses and Freezed:

@DataRepository([], remote: false)
class Node with DataModel<Node>, _$Node {
  factory Node(
      {int? id,
      String? name,
      @DataRelationship(inverse: 'children') BelongsTo<Node>? parent,
      @DataRelationship(inverse: 'parent') HasMany<Node>? children}) = _Node;
  factory Node.fromJson(Map<String, dynamic> json) => _$NodeFromJson(json);

Remove a relationship

Given a Post with many Comments we want to remove:

final postWithNoComments = post.copyWith(comments: HasMany.remove()).was(post);

Works with both HasMany and BelongsTo.

Removing a relationship does not delete its linked resources (the actual comments in this case).

Relationship extensions

A User with Tasks could be created like this:

final t1 = Task(title: 'do 1');
final t2 = Task(title: 'do 2');
final user = User(name: 'Frank', tasks: HasMany({t1, t2}));

// or using an extension on Set<DataModel>

final user = User(name: 'Frank', tasks: {t1, t2}.asHasMany);

or a Task with User:

final user = User(name: 'Frank');
final task = Task(title: 'do 1', user: BelongsTo(user));

// or using an extension on DataModel

final task = Task(title: 'do 1', user: user.asBelongsTo);

Need professional help with Flutter?

Describe your project in detail and include your e-mail and budget.