MobX in Flutter

At the heart of MobX are three important concepts: Observables, Actions, and Reactions.

MobX for the Dart language.

Supercharge the state-management in your Dart apps with Transparent Functional Reactive Programming (TFRP)

MobX is a state-management library that makes it simple to connect the reactive data of your application with the UI. This wiring is completely automatic and feels very natural. As the application developer, you focus purely on what reactive data needs to be consumed in the UI (and elsewhere) without worrying about keeping the two in sync.

It’s not really magic but it does have some smarts around what is being consumed (observables) and where (reactions), and automatically tracks it for you. When the observables change, all reactions are re-run. What’s interesting is that these reactions can be anything from a simple console log, a network call to re-rendering the UI.

MobX has been a very effective library for the JavaScript apps and this port to the Dart language aims to bring the same levels of productivity.

Core Concepts

dependencies:
flutter:
sdk: flutter
mobx: ^0.3.5
flutter_mobx: ^0.3.0+1
shared_preferences: ^0.5.3+4

Then replace the dev_dependencies section with this snippet of code:

dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^1.6.5
mobx_codegen: ^0.3.3+1

Now, run this command in your project’s root directory to download the dependencies:

$ flutter packages get

At the heart of MobX are three important concepts: Observables, Actions, and Reactions.

 

A simple reactive-counter is represented by the following observable:

import 'package:mobx/mobx.dart';
final counter = Observable(0);

 

On first sight, this does look like some boilerplate code which can quickly go out of hand! This is why we added mobx_codegen to the mix that allows you to replace the above code with the following:

 

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++;
}
}

 

Computed Observables

What can be derived, should be derived. Automatically.

The state of your application consists of core-state and derived-state. The core state is the state inherent to the domain you are dealing with. For example, if you have a Contact entity, the firstName and lastName form the core-state of Contact. However, fullName is derived-state, obtained by combining firstName and lastName.

Such a derived state, that depends on a core state or other derived state is called a Computed Observable. It is automatically kept in sync when its underlying observables change.

State in MobX = Core-State + Derived-State


import
'package:mobx/mobx.dart';
part 'counter.g.dart';
class Contact = ContactBase with _$Contact;abstract class ContactBase with Store {
@observable
String firstName;
@observable
String lastName;
@computed
String get fullName => '$firstName, $lastName';
}

In the example above fullName is automatically kept in sync if either firstName and/or lastName changes.

Observer

One of the most visual reactions in the app is the UI. The Observer widget (which is part of the flutter_mobx package), provides a granular observer of the observables used in its builder function. Whenever these observables change, Observer rebuilds and renders.

Below is the Counter example in its entirety.

import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
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++;
}
}
class CounterExample extends StatefulWidget {
const CounterExample({Key key}) : super(key: key);

@override

_CounterExampleState createState() => _CounterExampleState();
}
class _CounterExampleState extends State<CounterExample> {
final _counter = Counter();

@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
title: const Text('Counter'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Observer(
builder: (_) => Text(
'${_counter.value}',
style: const TextStyle(fontSize: 20),
)),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _counter.increment,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}

autorun

Autorun will run every time specific observable changes. For example, if we wanted to print the value of the counter every time it changes, we could do something like this:

autorun((_) {
print(counter.value);
});

Reaction

Reaction is similar to autorun, but it gives us more control when tracking observables’ values. It receives two arguments: the first is a simple function to return the data used in the second argument.

The second argument will be the effect function; this effect function will only react to data passed in the first function argument. This effect function will only be triggered when the data you passed in the first argument has changed.

reaction((_) {
return counter.value;
}, (_) {
print('Counter changed to ${counter.value}');
});

when

When is very similar to reaction, but it’s more specific. It’s a function that will only react to data matching the data you pass in the first argument.

when((_) {
return counter.value == 0;
}, (_) {
print('Counter is zero');
});
0
Would love your thoughts, please comment.x
()
x