Flutter MobX as State Management: quick guide

What is MobX?

MobX is a package used for state management in Flutter. But to understand what it does, first you need to understand what state management is.

What is State management?

State management is a tool you can use to control the state of your screen. If you have a button, for example, that every time is pressed has to perform an action and this action changes a background color, then this button has to change the color state of your screen, taking the param backgroundColor from one state to another. When you have many states to control you can start considering using a state manager.

But be careful, you don’t always need a state manager. Most of the time you can easily change the state of your screen with a simple tool called setState or ChangeNotifier already available in Flutter without having to use external packages.

The main advantage of state management is to isolate the state control from the interface, making the interface more clean. Using good state management also has the advantage of making the state of a variable available to all pages. This way you can have an avatar picture that will be available to your entire app, for example.

Why MobX?​

MobX is a great option when your app gets bigger. It’s quick for everyone to understand since its syntax is easy and it keeps your project organized because you separate your state management from the User Interface. 

This is a guide for anyone who wants to use MobX to get started quickly.

To start you have two options: you can create a class and write all the code for mobX or you can use mobx_codegen to generate all the boilerplate. I have to say that it’s not a lot of boilerplate, but codegen makes it much easier, so this guide will be using this generator. This way, you can write less code.

How to do it

Importing your dependencies

First of all you need to import in your pubspec.yaml these dependencies:

dependencies:

    mobx: 

    flutter_mobx:

dev_dependencies:

    build_runner:

    mobx_codegen:

Observables and Actions

Secondly you need to create a class that extends the Store class from MobX, like this:

import ‘package:mobx/mobx.dart’;

partcontact.g.dart’;

class Contact = ContactBase with _$Contact;

abstract class ContactBase with Store {

  @observable

  String firstName = ‘ ‘;

  @observable

  String lastName = ‘ ‘;

  @action

  void changeFirstName(String newFirstName) {

    firstName = newFirstName;

  }

  @action

  void changeLastName(String newLastName) {

    lastName = newLastName;

  }

}

You have to write this line part ‘contact.g.dart’ because this way the codegen will be able to generate this file for us. The name of this file should be like the one which contains this store. For example, if you called this Store contact.dart, this part file should be contact.g.dart.

In this example you can see that we’ve created a variable (observable) that will be observed by mobX and a method (action) that will change the state of that variable.

This is a pattern for mobX: you shouldn’t change the state of a variable directly, you must always change it through an action. This way you have more control over your states.

So, you need to make a mixin linking this class Contact = ContactBase with _$Contact because this way you are joining the file generated with this Store you’ve created.

Computeds

In MobX you can also have computeds which are a combination of two or more observables, like in this example:

import ‘package:mobx/mobx.dart’;

part ‘contact.g.dart’;

class Contact = ContactBase with _$Contact;

abstract class ContactBase with Store {

 @observable

 String firstName = ‘ ‘;

 @observable

 String lastName = ‘ ‘;

 @computed

 String get fullName => $firstName, $lastName;

}

Don’t forget the word get in your computed, ok?

Once you’ve finished writing your code, you need to type a command line into your terminal to generate the contact.g.dart file for you, you have these options:

flutter pub run build_runner build

flutter pub run build_runner watch

When you use the watch command your part file will be regenerated every time something changes on your Store code. When you use the build command, it will be generated only once, after a new change you will need to run this command again.

With MobX you also have autorun and reactions.

Autoruns

Autorun is a method that runs when it is declared and every time an observable inside it changes. As in this example, when the observable variable greeting changes its value, the autorun is triggered:

import ‘package:mobx/mobx.dart’;

String greeting = Observable(‘Hello Leticia’);

final dispose = autorun((_){

  print(greeting.value);

});

greeting.value = MobX is great’;

// Done with the autorun()

dispose();

// Output:

// Hello Leticia

// MobX is great

Reactions

Reactions work almost the same as autorun, but they are only triggered when something (observable) changes inside it, not when they are declared, like in this example:

import ‘package:mobx/mobx.dart’;

String greeting = Observable(‘Hello Leticia’);

final dispose = reaction((_) => greeting.value, (msg) => print(msg));

greeting.value = ‘MobX is great’

// Done with the reaction()

dispose();

// Output:

// MobX is great

User Interface

When you use this Store in your User Interface, you have to wrap your widget tree with an Observer (from MobX), this way the variables you access from your Store will be observed:

class ContactPage extends StatelessWidget {

  final _contact = Contact();

  @override

  Widget build(BuildContext context) => Scaffold(

        appBar: AppBar(

          title: Text(‘Contact’),

        ),

        body: Center(

          child: Column(

            mainAxisAlignment: MainAxisAlignment.center,

            children: <Widget>[

              TextFormField(

                decoration: InputDecoration(labelText: ‘First Name’),

                onChanged: _contact.changeFirstName,

              ),

              TextFormField(

                decoration: InputDecoration(labelText: ‘Last Name’),

                onChanged: _contact.changeLastName,

              ),

              Text(

                ‘The contact full name is:’,

              ),

              Observer(

                  builder: (_) => Text(

                        ‘${_contact.fullName}’,

                        style: TextStyle(fontSize: 20),

                      )),

            ],

          ),

        ),

      );

}

This is the result:

Flutter MobX

Dependency injection

If you need to make the Store class available for other screens, you need to use dependency injection. You can do that with Modular, GetIt or Provider. But I’m going to show you how to use GetIt as it’s the most simple one so you can understand more quickly what dependency injection is about.

First you have to import GetIt in your pubspec.yaml:

dependencies:

    get_it:

Once you’ve imported Getlt, then you should inject all your store classes into your main function making them a Singleton, like this:

void main() {

     GetIt getIt = GetIt.I;

     getIt.registerSingleton<Contact>(Contact());

}

Then, on the screen you’re going to use this class, you can get this only instance this way:

class ContactPage extends StatelessWidget {

  final _contact = GetIt.I.get<Contact>();

  @override

  Widget build(BuildContext context) => Scaffold(

        appBar: AppBar(

          title: Text(‘Contact’),

        ),

        body: Center(

          child: Column(

            mainAxisAlignment: MainAxisAlignment.center,

            children: <Widget>[

              TextFormField(

                decoration: InputDecoration(labelText: ‘First Name’),

                onChanged: _contact.changeFirstName,

              ),

              TextFormField(

                decoration: InputDecoration(labelText: ‘Last Name’),

                onChanged: _contact.changeLastName,

              ),

              Text(

                ‘The contact full name is:’,

              ),

              Observer(

                  builder: (_) => Text(

                        ‘${_contact.fullName}’,

                        style: TextStyle(fontSize: 20),

                      )),

            ],

          ),

        ),

      );

}

You have to use dependency injection to do this because if you instantiate your Contact class every time you need to use it, every time it would be initialized with the default values. If you want to access the same variable value on another screen you need to keep that information inside a Singleton (only one instance).

See how easy it is to use MobX? Try it next time you start a new project.

You can also check out the MobX documentation to get started.

Subscribe To Our Newsletter

Get updates and learn from the best

More To Explore

Articles

StatelessWidgets vs StatefulWidgets

When developing with Flutter, there will certainly be a need of building screen layouts, customizing existent user interfaces or managing how the screen changes with

Let us know how we can add value to your business

Book a meeting with an Expert today

Calm Experts - workspace
site background image

Let's have a chat

Learn how we Can help from here to launch.