Skip to main content

JSON Serialization of Stores

The pub 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

pub and pubpackages. 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.