Skip to main 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, pub.
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.

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:

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:

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(
child: Observer(
builder: (_) =>
Image.asset('images/dice${diceCounter.left}.png'),
),
onPressed: diceCounter.roll,
),
),
Expanded(
child: TextButton(
child: Observer(
builder: (_) =>
Image.asset('images/dice${diceCounter.right}.png'),
),
onPressed: diceCounter.roll,
),
),
],
),
Padding(
padding: const EdgeInsets.all(16),
child: Observer(
builder: (_) => Text(
'Total ${diceCounter.total}',
style: const TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black87,
fontSize: 16,
fontFamily: 'Verdana'),
),
),
),
],
),
);
}
}

Summary

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

info

See the complete code here.