Models

Flutter Data models are data classes that mix DataModel in:

@DataRepository([TaskAdapter])
@JsonSerializable()
class Task with DataModel<Task> {
  @override
  final int? id;
  final String title;
  final bool completed;

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

This enforces the implementation of the id getter. Use the type that better suits your data: int? and String? are the most common.

The json_serializable library is helpful but not required.

  • Model with @JsonSerializable? You don’t need to declare fromJson or toJson
  • Model without @JsonSerializable? You must declare fromJson and toJson

If you choose it, you can make use of @JsonKey and other configuration parameters as usual. A common use-case is having a different remote id attribute such as _objectId. Annotating id with @JsonKey(name: '_objectId') takes care of it.

Extension methods

In addition, various useful methods become available on the class:

init

The init call is necessary when using new models (and only then) in order to register them within Flutter Data. It takes a Riverpod Reader as sole argument, typically ref.read or container.read.

final user = User(id: 1, name: 'Frank').init(ref.read);

Important: Any Dart file that wants to use these extensions must import the library!

import 'package:flutter_data/flutter_data.dart';

VSCode protip! Type Command + . over the missing method and choose to import!

was

It inits a model copying the identity of supplied model.

Useful for model updates:

final steve = frank.copyWith(name: 'Steve').was(frank);

save

final user = User(id: 1, name: 'Frank').init(ref.read);
await user.save();

The call is syntax-sugar for Repository#save and takes the same arguments except the model: remote, headers, params, onError.

delete

final user = await repository.findOne(1);
await user.delete();

It’s syntax-sugar for Repository#delete and takes the same arguments (except the model).

Notice that init was not necessary here because the user already came from a repository.

find

final updatedUser = await user.find();

It’s syntax-sugar for Repository#findOne and takes the same arguments (except the ID).

Freezed support

Here’s an example:

@freezed
@DataRepository([JSONAPIAdapter, BaseAdapter])
class City with DataModel<City>, _$City {
  const City._();
  factory City({int id, String name}) = _City;
  factory City.fromJson(Map<String, dynamic> json) => _$CityFromJson(json);
}

Unions haven’t been tested yet.

Polymorphic models

An example where Staff and Customer are both Users:

abstract class User<T extends User<T>> with DataModel<T> {
  final String id;
  final String name;
  User({this.id, this.name});
}

@JsonSerializable()
@DataRepository([JSONAPIAdapter, BaseAdapter])
class Customer extends User<Customer> {
  final String abc;
  Customer({String id, String name, this.abc}) : super(id: id, name: name);
}

@JsonSerializable()
@DataRepository([JSONAPIAdapter, BaseAdapter])
class Staff extends User<Staff> {
  final String xyz;
  Staff({String id, String name, this.xyz}) : super(id: id, name: name);
}

Need professional help with Flutter?

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