Flutter Data models are data classes that extend DataModel
and are annotated with @DataRepository
:
@DataRepository([TaskAdapter])
@JsonSerializable()
class Task extends DataModel<Task> {
@override
final int? id;
final String title;
final bool completed;
Task({this.id, required this.title, this.completed = false});
}
DataModel
automatically registers new data classes within the framework and enforces the implementation of an id
getter. Use the type that better suits you: int?
and String?
are the most common.
The json_serializable
library is helpful but not required.
@JsonSerializable
? You don’t need to declare fromJson
or toJson
@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.
Here’s an example:
@freezed
@DataRepository([TaskAdapter])
class Task extends DataModel<Task> with _$Task {
Task._();
factory Task({
int? id,
required String name,
required BelongsTo<User> user,
}) = _Task;
factory Task.fromJson(Map<String, dynamic> json) => _$TaskFromJson(json);
}
Unions haven’t been tested yet.
In order to omit an attribute simply use @JsonKey(ignore: true)
.
In addition, various useful methods become available on the class:
final user = User(id: 1, name: 'Frank');
await user.save();
The call is syntax-sugar for Repository#save and takes the same arguments (except the model).
Or, saving locally (i.e. remote: false
) with a sync API:
final user = User(id: 1, name: 'Frank');
user.saveLocal();
final user = await repository.findOne(1);
await user.delete();
It is syntax-sugar for Repository#delete and takes the same arguments (except the model).
Or, deleting locally (i.e. remote: false
) with a sync API:
final user = User(id: 1, name: 'Frank');
user.deleteLocal();
final updatedUser = await user.find();
It’s syntax-sugar for Repository#findOne and takes the same arguments (except the model/ID).
Or, reloading locally (i.e. remote: false
) with a sync API:
final user = User(id: 1, name: 'Frank');
final user2 = user.reloadLocal();
Used whenever we need to transfer identity to a model without identity (that is, without an ID).
final user = User(id: 1, 'Parker');
final user2 = user.copyWith(name: 'Frank').withKeyOf(user);
id
will still be null
but saving and retreiving will work:
await user2.save(remote: false);
final user3 = await user2.find();
// user3.id == 1
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!
You can also disable them by hiding the extension:
import 'package:flutter_data/flutter_data.dart' hide DataModelExtension;
An example where Staff
and Customer
are both User
s:
abstract class User<T extends User<T>> extends 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);
}