Skip to content

Dice ​

In this example, we will build a fun dice app. You can make the dice roll at the tap of a button and a random count is generated with every roll of dice.

INFO

See the complete code here.

Store setup ​

The DiceCounter store is quite simple. It keeps a track of dice values and the total count. Below is how it looks:

  • Generate dice_counter.g.dart file using build_runner and this must be imported into the store. Read more about build_runner in the MobX Code Generation library, mobx_codegen on pub.dev.
dart
import 'dart:math';
import 'package:mobx/mobx.dart';

part 'dice_counter.g.dart';

class DiceCounter = _DiceCounter with _$DiceCounter;

abstract class _DiceCounter with Store {
  @observable
  int left = Random().nextInt(6) + 1;

  @observable
  int right = Random().nextInt(6) + 1;

  @observable
  int total;

  @action
  void roll() {
    left = Random().nextInt(6) + 1;
    right = Random().nextInt(6) + 1;
    total = left + right;
  }
}
  • In the above store left and right are the dice counts defined with @observable annotations and are initialised with a random number ranging from 1 - 6.
  • total is also an observable which keeps track of the total count of the dice.
  • The action-method roll() defined with @action annotation is used to update the dice counts every time user taps on the dice.

With the above implementation I was able to keep a track of the left, right and total counts with every user interaction as expected or so I thought 🤔.

Discovering @computed ​

I ran into an issue... The total count was null for the very first time. It gets updated only when the action-method roll() is called.

In search of the solution I went through the documentation and few examples and realised there is an insanely easy way to get around this issue. @computed BOOM! my issue is solved.

I have modified the above DiceCounter implementation using @computed.

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

part 'dice_counter.g.dart';

class DiceCounter = _DiceCounter with _$DiceCounter;

abstract class _DiceCounter with Store {
  @observable
  int left = Random().nextInt(6) + 1;

  @observable
  int right = Random().nextInt(6) + 1;

  @computed
  int get total => left + right;

  @action
  void roll() {
    left = Random().nextInt(6) + 1;
    right = Random().nextInt(6) + 1;
  }
}
  • Now total is a computed observable annotated with @computed. Computed observables are in-sync every time left or right count is updated.
  • The value of total is automatically updated when the instance of the store is created so I no longer have the null value when I load the app.
  • @computed does more than that. Now, I do not have to write any additional code like the above in the action-method to update it.

It exactly does what the author of mobx quoted... "What can be derived, should be derived. Automatically".

Integrating the Store with the View ​

Now that the DiceCounter store is ready, it's time to add it to the Widget to see the magic happen. Let's create an instance of our store:

dart
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';

import 'dice_counter.dart';

final diceCounter = DiceCounter();

The observables and actions from the store can be accessed via the newly created instance diceCounter along with the Observer widget as shown below:

dart
class DiceView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final diceCounter = Provider.of<DiceCounter>(context);

    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Row(
            children: <Widget>[
              Expanded(
                child: TextButton(
            // highlight-start
                  child: Observer(
                    builder: (_) =>
                        Image.asset('images/dice${diceCounter.left}.png'),
                  ),
            // highlight-end
                  onPressed: diceCounter.roll,
                ),
              ),
              Expanded(
                child: TextButton(
            // highlight-start
                  child: Observer(
                    builder: (_) =>
                        Image.asset('images/dice${diceCounter.right}.png'),
                  ),
            // highlight-end
                  onPressed: diceCounter.roll,
                ),
              ),
            ],
          ),
          Padding(
            padding: const EdgeInsets.all(16),
            // highlight-start
            child: Observer(
              builder: (_) => Text(
                'Total ${diceCounter.total}',
                style: const TextStyle(
                    fontWeight: FontWeight.bold,
                    color: Colors.black87,
                    fontSize: 16,
                    fontFamily: 'Verdana'),
              ),
            ),
            // highlight-end
          ),
        ],
      ),
    );
  }
}

Summary ​

The working example will be as seen in the figure below:

INFO

See the complete code here.

Made with care for the Dart & Flutter community. MIT licensed.