JSON Serialization of Stores
The package is a popular way to
encode/decode between json representations of your models. It works by attaching
the @JsonSerializable()
annotation to the Store
classes. Since this is a
custom annotation, we have to invoke the build_runner
command, just like we do
for mobx_codegen
.
Let's add support for json_serializable
to the todos example.
See the complete code here.
Adding dependency in pubspec.yaml
The first step is to include the dependency on the
and packages. We add this to the `pubspec.yaml` and run `flutter pub get` to download it.dependencies:
json_serializable: ^6.3.2
json_annotation: ^4.6.0
Adding annotations
To make our store classes travel to JSON and back, we need to annotate them with
@JsonSerializable()
:
import 'package:json_annotation/json_annotation.dart';
import 'package:mobx/mobx.dart';
part 'todo.g.dart';
()
class Todo extends _Todo with _$Todo {
Todo(String description) : super(description);
}
enum VisibilityFilter { all, pending, completed }
()
class TodoList extends _TodoList with _$TodoList {}
abstract class _TodoList with Store {
()
ObservableList<Todo> todos = ObservableList<Todo>();
VisibilityFilter filter = VisibilityFilter.all;
String currentDescription = '';
ObservableList<Todo> get pendingTodos =>
ObservableList.of(todos.where((todo) => todo.done != true));
ObservableList<Todo> get completedTodos =>
ObservableList.of(todos.where((todo) => todo.done == true));
bool get hasCompletedTodos => completedTodos.isNotEmpty;
bool get hasPendingTodos => pendingTodos.isNotEmpty;
String get itemsDescription {
if (todos.isEmpty) {
return "There are no Todos here. Why don't you add one?.";
}
final suffix = pendingTodos.length == 1 ? 'todo' : 'todos';
return '${pendingTodos.length} pending $suffix, ${completedTodos.length} completed';
}
(ignore: true)
ObservableList<Todo> get visibleTodos {
switch (filter) {
case VisibilityFilter.pending:
return pendingTodos;
case VisibilityFilter.completed:
return completedTodos;
default:
return todos;
}
}
bool get canRemoveAllCompleted =>
hasCompletedTodos && filter != VisibilityFilter.pending;
bool get canMarkAllCompleted =>
hasPendingTodos && filter != VisibilityFilter.completed;
void addTodo(String description) {
final todo = Todo(description);
todos.add(todo);
currentDescription = '';
}
void removeTodo(Todo todo) {
todos.removeWhere((x) => x == todo);
}
void removeCompleted() {
todos.removeWhere((todo) => todo.done);
}
void markAllAsCompleted() {
for (final todo in todos) {
todo.done = true;
}
}
}
Custom Converters
In case of special types like ObservableList<Todo>
, which are not directly
serializable into JSON, we can write custom converters by extending the
JsonConverter<T, S>
type, as shown below:
import 'package:json_annotation/json_annotation.dart';
import 'package:mobx/mobx.dart';
import 'package:mobx_examples/todos/todo.dart';
class ObservableTodoListConverter extends JsonConverter<ObservableList<Todo>,
Iterable<Map<String, dynamic>>> {
const ObservableTodoListConverter();
ObservableList<Todo> fromJson(Iterable<Map<String, dynamic>> json) =>
ObservableList.of(json.map(Todo.fromJson));
Iterable<Map<String, dynamic>> toJson(ObservableList<Todo> object) =>
object.map((element) => element.toJson());
}
This converter is then used for the todos
property as shown below:
abstract class _TodoList with Store {
()
ObservableList<Todo> todos = ObservableList<Todo>();
// ...
}
On with the code-generation
With these changes, let's run the build_runner
command in the project folder:
dart pub run build_runner watch --delete-conflicting-outputs
This will generate todo.g.dart
and todo_list.g.dart
files.
JSON Serialization / Deserialization
final list = TodoList();
expect(list.todos.length, equals(0));
list..addTodo('first one')..addTodo('second one');
const targetJson = '''
{
"todos": [
{
"description": "first one",
"done": false
},
{
"description": "second one",
"done": false
}
],
"filter": "VisibilityFilter.all",
}''';
final listJson = list.toJson();
expect(listJson, targetJson);
final listInstance = TodoList.fromJson(listJson);
expect(list.todos.length, listInstance.todos.length);
expect(list.canMarkAllCompleted, listInstance.canMarkAllCompleted);
expect(list.itemsDescription, listInstance.itemsDescription);
Summary
With these changes, you should now be able to serialize the Todos to/from
JSON
✌️. BTW, its worth noting that mobx_codegen
can co-exist with other
generators.
See the complete code here.