Need help with mobx.dart?
Click the β€œchat” button below for chat support from the developer who created it, or find similar developers for support.

About the developer

mobxjs
1.9K Stars 219 Forks MIT License 931 Commits 66 Opened issues

Description

MobX for the Dart language. Hassle-free, reactive state-management for your Dart and Flutter apps.

Services available

!
?

Need anything else?

Contributors list

Language: English | PortuguΓͺs | Chinese

mobx.dart



pub package pub package pub package

Build Status Publish Coverage Status Netlify Status

Join the chat at https://discord.gg/dNHY52k

MobX for the Dart language.

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

Introduction

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.

Sponsors

We are very thankful to our sponsors to make us part of their Open Source Software (OSS) program. [Become a sponsor]

Get Started

Follow along with the Getting Started guide on the MobX.dart Website.

Go deep

For a deeper coverage of MobX, do check out MobX Quick Start Guide. Although the book uses the JavaScript version of MobX, the concepts are 100% applicable to Dart and Flutter.

Core Concepts

MobX Triad

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

Observables

Observables represent the reactive-state of your application. They can be simple scalars to complex object trees. By defining the state of the application as a tree of observables, you can expose a reactive-state-tree that the UI (or other observers in the app) consume.

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

import 'package:mobx/mobx.dart';

final counter = Observable(0);

More complex observables, such as classes, can be created as well.

class Counter {
  Counter() {
    increment = Action(_increment);
  }

final _value = Observable(0); int get value => _value.value;

set value(int newValue) => _value.value = newValue; Action increment;

void _increment() { _value.value++; } }

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

Note the use of annotations to mark the observable properties of the class. Yes, there is some header boilerplate here but its fixed for any class. As you build more complex classes this boilerplate will fade away and you will mostly focus on the code within the braces.

Note: Annotations are available via the mobx_codegen package.

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 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 derived state, that depends on 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 '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';

}

In the example above

fullName
is automatically kept in sync if either

firstName
and/or
lastName
changes.

Actions

Actions are how you mutate the observables. Rather than mutating them directly, actions add a semantic meaning to the mutations. For example, instead of just doing

value++
, firing an
increment()
action carries more meaning. Besides, actions also batch up all the notifications and ensure the changes are notified only after they complete. Thus the observers are notified only upon the atomic completion of the action.

Note that actions can also be nested, in which case the notifications go out when the top-most action has completed.

final counter = Observable(0);

final increment = Action((){ counter.value++; });

When creating actions inside a class, you can take advantage of annotations!

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

Asynchronous Actions

MobX.dart handles asynchronous actions automatically and does not require wrapping the code with

runInAction
.

@observable
String stuff = '';

@observable loading = false;

@action Future loadStuff() async { loading = true; //This notifies observers stuff = await fetchStuff(); loading = false; //This also notifies observers }

Reactions

Reactions complete the MobX triad of observables, actions and reactions. They are the observers of the reactive-system and get notified whenever an observable they track is changed. Reactions come in few flavors as listed below. All of them return a

ReactionDisposer
, a function that can be called to dispose the reaction.

One striking feature of reactions is that they automatically track all the observables without any explicit wiring. The act of reading an observable within a reaction is enough to track it!

The code you write with MobX appears to be literally ceremony-free!

ReactionDisposer autorun(Function(Reaction) fn)

Runs the reaction immediately and also on any change in the observables used inside

fn
.
import 'package:mobx/mobx.dart';

String greeting = Observable('Hello World');

final dispose = autorun((_){ print(greeting.value); });

greeting.value = 'Hello MobX';

// Done with the autorun() dispose();

// Prints: // Hello World // Hello MobX

ReactionDisposer reaction(T Function(Reaction) predicate, void Function(T) effect)

Monitors the observables used inside the

predicate()
function and runs the
effect()
when the predicate returns a different value. Only the observables inside
predicate()
are tracked.
import 'package:mobx/mobx.dart';

String greeting = Observable('Hello World');

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

greeting.value = 'Hello MobX'; // Cause a change

// Done with the reaction() dispose();

// Prints: // Hello MobX

ReactionDisposer when(bool Function(Reaction) predicate, void Function() effect)

Monitors the observables used inside

predicate()
and runs the
effect()
when it returns
true
. After the
effect()
is run,
when
automatically disposes itself. So you can think of when as a one-time
reaction
. You can also dispose
when()
pre-maturely.
import 'package:mobx/mobx.dart';

String greeting = Observable('Hello World');

final dispose = when((_) => greeting.value == 'Hello MobX', () => print('Someone greeted MobX'));

greeting.value = 'Hello MobX'; // Causes a change, runs effect and disposes

// Prints: // Someone greeted MobX

Future asyncWhen(bool Function(Reaction) predicate)

Similar to

when
but returns a
Future
, which is fulfilled when the
predicate()
returns true. This is a convenient way of waiting for the
predicate()
to turn
true
.
final completed = Observable(false);

void waitForCompletion() async { await asyncWhen(() => _completed.value == true);

print('Completed'); }

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 { final _counter = Counter();

@override Widget build(BuildContext context) => Scaffold( appBar: AppBar( title: const Text('Counter'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ 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), ), ); }

Contributing

If you have read up till here, then πŸŽ‰πŸŽ‰πŸŽ‰. There are couple of ways in which you can contribute to the growing community of

MobX.dart
.
  • Pick up any issue marked with "good first issue"
  • Propose any feature, enhancement
  • Report a bug
  • Fix a bug
  • Participate in a discussion and help in decision making
  • Write and improve some documentation. Documentation is super critical and its importance cannot be overstated!
  • Send in a Pull Request :-)
  • Chime in and Join the chat at https://discord.gg/dNHY52k

Contributors ✨

All Contributors <!-- ALL-CONTRIBUTORS-BADGE:END -->

Thanks goes to these wonderful people (emoji key):


Pavan Podila

πŸ’» πŸ“– πŸ‘€

katis

πŸ’» πŸ€” πŸ‘€ ⚠️

Scott Hyndman

πŸ’» πŸ€” πŸ‘€ ⚠️

Michael Bui

πŸ’» πŸ“– πŸ‘€ πŸ’‘

Remi Rousselet

πŸ’» πŸ“– πŸ‘€

adiaKhaitan

πŸ“–

Jacob Moura

πŸ’» πŸ“– 🌍

Daniel Albuquerque

🌍

Marco Scannadinari

πŸ“–

lsaudon

πŸ’» πŸ“–

Efthymis Sarmpanis

πŸ’»

Giri Jeedigunta

πŸ“– πŸ’‘

Hebri Ramnath Nayak

πŸ“–

Robert Brunhage

πŸ“–

Brady Trainor

πŸ“–

Kushagra Saxena

πŸ“– πŸ’‘

Pedro Massango

🌍

Peter Czibik

πŸ’»

Luan Nico

πŸ“–

Kobi

πŸ’»

Ryan

πŸ“–

Ivan Terekhin

πŸ’»

Yoav RofΓ©

πŸ“–

Mateusz Wojtczak

πŸ“–

Timur Artikov

πŸ’»

Saurabh Sohoni

πŸ“–

renanzdm

πŸ“–

Rachman Chavik

πŸ’»

Nathan Cabasso

πŸ› πŸ’»

geisterfurz007

πŸ“– πŸ–‹

Romuald Barbe

πŸ’»

Alexander Mazuruk

πŸ’‘

Alberto Bonacina

πŸ“–

Roland Ibragimov

πŸ“–

zyzhao

πŸ“–

Xinhai Wang

πŸ“–

Henry Mayer

πŸ’» ⚠️

Sergey

πŸ’» ⚠️

Jason Rai

πŸ“–

Joshua de Guzman

πŸ’‘

Jan Hertlein

πŸ“–

Evo Stamatov

πŸ’»

Davi Eduardo

πŸ“–

Leonardo Custodio

πŸ’» πŸ“–

Prince Srivastava

πŸ’‘ πŸ’»

Muhammad Muhajir

πŸ“–

D

πŸ“–

David Martos

πŸ’»

Issa Nimaga

πŸ“–

AscΓͺnio

πŸ’»

This project follows the all-contributors specification. Contributions of any kind welcome!

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.