DEV Community

Cover image for Finite State Machine in Dart with the state_machine module
Mathieu Kerjouan
Mathieu Kerjouan

Posted on

Finite State Machine in Dart with the state_machine module

You already know, Erlang is my favorite language... You should also probably know Erlang uses finite state machines as one of its core concept? In fact, it's probably why one can't come back from Erlang, when you know how to use them, everything becomes easier. This kind of pattern is not only available in Erlang, and few implementations exist on Dart. Here the interesting ones found on pub.dev and github:

The most used of those project is state_machine with more than 22k download, then, this will be the first one to be tested in this article.

Bootstrapping

$ dart create fsm
Creating fsm using template console...

  .gitignore
  analysis_options.yaml
  CHANGELOG.md
  pubspec.yaml
  README.md
  bin/fsm.dart
  lib/fsm.dart
  test/fsm_test.dart

Running pub get...                     1.1s
  Resolving dependencies...
  Downloading packages...
  Changed 48 dependencies!

Created project fsm in fsm! In order to get started, run the following commands:

  cd fsm
  dart run

$ cd fsm

$ dart pub add state_machine
Resolving dependencies... 
Downloading packages... 
+ clock 1.1.2
+ intl 0.20.3
+ js 0.6.7 (0.7.2 available)
+ state_machine 3.0.3
+ w_common 4.0.0
Changed 5 dependencies!
1 package has newer versions incompatible with dependency constraints.
Try `dart pub outdated` for more information.
Enter fullscreen mode Exit fullscreen mode

The only required header to add in all your code requiring this module:

import 'package:state_machine/state_machine.dart';
Enter fullscreen mode Exit fullscreen mode

Usage

The first step is to create a new StateMachine object. Only one argument is required for the constructor, the state machine name.

When a state machine changes its state, the onStateChange parameter is receiving the event, and then, react on it.

// create a new state machine
StateMachine fsm = StateMachine('fsm');

// react on state change with a closure
final onStateChange = (StateChange change) {
    print('onStateChange: $change');
};  
fsm.onStateChange.listen(onStateChange);
Enter fullscreen mode Exit fullscreen mode

The second step is to create a new State objects representing respectively all accepted state by the state machine. To do that, the newState() method must be called. The only argument to pass is the name of the state.

During a transition, the state machine can enter into a new state or leave an old state. Those two kind of events can be followed by configuring respectively the onEnter and onLeave parameters.

// enter state closure
final onEnter = (StateChange change) {
  print('onEnter: $change');
};

// leave state closure
final onLeave = (StateChange change) {
  print('onLeave: $change');
};  

// create a state called init
State init = fsm.newState('init');
init.onEnter.listen(onEnter);
init.onLeave.listen(onLeave);

// create a state called count
State count = fsm.newState('count');
count.onEnter.listen(onEnter);
count.onLeave.listen(onLeave);

// create a state called stop
State stop = fsm.newState('stop');
stop.onEnter.listen(onEnter);
stop.onLeave.listen(onLeave);
Enter fullscreen mode Exit fullscreen mode

The transition between states can then be created. They are represented by StateTransition objects. This is the most "complex" method there, because the method newStateTransition requires 3 arguments. One is the name of the transition as a string, the second is the list of allowed states before the transition, and the last one is the next state after the transition.

StateTransition counting = fsm.newStateTransition('counting', [init,count], count);
StateTransition stopping = fsm.newStateTransition('stopping', [count], stop);
Enter fullscreen mode Exit fullscreen mode

Then, the state machine can be started by calling the start() method. The only argument used here is a reference to the starting state.

fsm.start(init);
Enter fullscreen mode Exit fullscreen mode

The transitions can now be called using the previous objects created. It is also possible to pass an argument to them as a payload.

// calling the counting transition
counting(1);
counting(2);
counting(3);
counting(4);

// calling the stopping transition
stopping();
Enter fullscreen mode Exit fullscreen mode

When the state machine is terminated, one should use the dispose() method to clean up the memory and avoid memory leaks.

fsm.dispose();
Enter fullscreen mode Exit fullscreen mode

The code looks okay. Let execute it to see what will happen.

$ dart run
...
onStateChange: StateChange: (none) --> init

onEnter: StateChange: (none) --> init

onStateChange: StateChange: init --> count
    payload: 1

onLeave: StateChange: init --> count
    payload: 1

onStateChange: StateChange: count --> count
    payload: 2

onLeave: StateChange: count --> count
    payload: 2

onStateChange: StateChange: count --> count
    payload: 3

onLeave: StateChange: count --> count
    payload: 3

onStateChange: StateChange: count --> count
    payload: 4

onLeave: StateChange: count --> count
    payload: 4

onStateChange: StateChange: count --> stop

onLeave: StateChange: count --> stop

onEnter: StateChange: count --> stop

onDispose: null
Enter fullscreen mode Exit fullscreen mode

As you can see, the different steps are displayed on the screen, and we can see each state transitions. If an illegal transition happens in a specific state, the state machine throw an exception.

Conclusion

state_machine just works, but the way the state machine, the transitions and the events triggering the transitions are created annoys me a bit, perhaps because it's the OOP way to do it.

Indeed, to me, a great implementation of a state machine is from Erlang, with gen_statem, a state machine should embed its "data" or "state" and those elements should be modified during a transition.

Anyway, if you want to know more about the state_machine package, here few links:

Have fun and happy hacking!


Cover Image by Mike Hindle on Unsplash

Top comments (1)

Collapse
 
topstar_ai profile image
Luis Cruz

The breakdown of using the state_machine module in Dart is quite enlightening, especially how you've highlighted the simplicity of bootstrapping a finite state machine. It's interesting to see how the onStateChange listener can effectively manage state transitions, which is crucial for building responsive applications. One potential improvement could be to explore integrating this with Dart’s built-in streams for more complex state handling scenarios. If you’re considering enhancements or new features for this state machine implementation, I’d be open to collaborating on that front!