Debugging your FSM

FSM2 provides a number of tools to help debug your FSM.

Firstly we strongly recommend that you only use the static transitions (i.e. don't use onDynamic). As yet we have found no use cases where onDynamic is needed and it makes debugging your FSM much harder.

Static Analysis

The first thing you should ALWAYS check is that your FSM is consistent.

You can do this by calling analysis().

statemachine.analysis();

You should create a unit test that tests your statemachine.

import 'package:fsm2/fsm2.dart';
import 'package:test/test.dart';

void main() {
  test('export', () async {
    final machine = _createMachine();
    expect(await machine.analyse(), equals(true));
  });
}

StateMachine _createMachine() {
  return StateMachine.create((g) => g
    ..initialState<Heating>()
    ..state<DoorOpen>((b) {})
    ..state<Heating>((b) => b
      ..on<OpenDoor, DoorOpen>()
      ..state<Toasting>((b) {})
      ..state<Baking>((b) {})));

}
class DoorOpen extends State {}
class Toasting extends State {}
class Baking extends State {}
class Heating extends State {}
class LightOn extends State {}
class OpenDoor extends Event {}
class OnTurnOff extends Event {}

Visualise your FSM

If you have followed our recommendation to not use 'onDynamic' then you can create a visualisation of your FSM.

void main() {
    final machine = _createMachine();
    await machine.export('test/test.gv');
  }

See details on visualisation for details on viewing the .gv file.

Simplify your FSM

We are not big fans of humongous state machines. Rather we recommend that you create a statemachine for each part of your code.

You might have an FSM for each screen or particular db updates but avoid the temptation to model your entire app as a single statemachine. This will never end well.

Log Transitions

The statemachine allows you to hook every transition by calling 'onTransition'.

  StateMachine.create((g) => g
      ..initialState<Solid>()
      ..state<Solid>((b) => b
        ..on<OnMelted, Liquid>(sideEffect: (e) => watcher.log(onMeltedMessage))
        ..onEnter((s, e) => watcher?.onEnter(s))
        ..onExit((s, e) => watcher?.onExit(s)))
      ..onTransition((fromState, event, toState) => print('${fromState} ${event} ${toState} ')));

The above code prints the eventType, fromState, and toState for every transition.

A single event can result in multiple transitions if your state machine is in a concurrent region.

Unit Testing

We provide a number of helper functions that make it easier to unit test your statemachine.

  • StateMachine.waitUntilQuiescent

  • StateMachine.isInState

  • StateMachine.stateOfMind


  test('test fsm', () async {
      final machine = _createMachine<Solid>(watcher);
      machine.applyEvent(OnMelted());
      // wait for the event to have been applied
      await machine.waitUntilQuiescent;
      expect(machine.isInState<Liquid>(), equals(true));
  });

Last updated