Annotations drastically simplify the usage of MobX by tastefully tucking away all the boilerplate in the generated files (*.g.dart
).
These examples use annotations, which is supported by the
mobx_codegen
package. To run the code-generator, we are using the following command:
$> cd $PATH_TO_MOBX_DART/mobx/example $> flutter packages pub run build_runner build
counter.dart
)A really simple Counter. After all this is how you start off a Dart/Flutter project :-)
import 'package:mobx/mobx.dart'; part 'counter.g.dart'; class Counter = CounterBase with _$Counter; abstract class CounterBase with Store { @observable int value = 0; @action void increment() { value++; } }
Notice the @observable
value
property and the @action
increment()
that mutates it.
todos.dart
)This example showcases some of the core features of MobX such as Observable
, Computed
and Action
. We have intentionally left out Reaction
to keep it simple.
The Todos example is a classic way of showcasing a framework or a library. Here, you can see how MobX can simplify your code.
A Todo
entity is at the heart of the Todos example. A bit of boilerplate is needed to make the code-generator do the rest for you.
import 'package:mobx/mobx.dart'; part 'example.g.dart'; class Todo = TodoBase with _$Todo; abstract class TodoBase with Store { TodoBase(this.description); @observable String description = ''; @observable bool done = false; }
The TodoList
manages the list of Todo
. Notice the use of annotations to make the code more readable. MobX follows the princple:
What can be derived, should be derived. Automatically.
This is achieved with a small core-state (the @observable
properties) and the derived-state (the @computed
properties).
enum VisibilityFilter { all, pending, completed } class TodoList = TodoListBase with _$TodoList; abstract class TodoListBase with Store { @observable ObservableList<Todo> todos = ObservableList<Todo>(); @observable VisibilityFilter filter = VisibilityFilter.all; @observable String currentDescription = ''; @computed ObservableList<Todo> get pendingTodos => ObservableList.of(todos.where((todo) => todo.done != true)); @computed ObservableList<Todo> get completedTodos => ObservableList.of(todos.where((todo) => todo.done == true)); @computed bool get hasCompletedTodos => completedTodos.isNotEmpty; @computed bool get hasPendingTodos => pendingTodos.isNotEmpty; @computed String get itemsDescription => '${pendingTodos.length} pending, ${completedTodos.length} completed'; @computed ObservableList<Todo> get visibleTodos { switch (filter) { case VisibilityFilter.pending: return pendingTodos; case VisibilityFilter.completed: return completedTodos; default: return todos; } } // ... }
Notice how you can have derived-state
that depends on other derived-state. For example, itemsDescription
depends on pendingTodos
and completedTodos
, which are also computed (aka derived) properties.
The operations that can be performed on the TodoList are marked with @action
. These are simple functions, which can be invoked like normal dart functions! There is no extra ceremony to invoke an action.
class TodoList = TodoListBase with _$TodoList; abstract class TodoListBase with Store { // ... @action void addTodo(String description) { final todo = Todo(description); todos.add(todo); currentDescription = ''; } @action void removeTodo(Todo todo) { todos.removeWhere((x) => x == todo); } @action void changeDescription(String description) => currentDescription = description; @action void changeFilter(VisibilityFilter filter) => this.filter = filter; @action void removeCompleted() { todos.removeWhere((todo) => todo.done); } @action void markAllAsCompleted() { for (final todo in todos) { todo.done = true; } } }