Skip to content
Ricardo Canastro edited this page Mar 6, 2022 · 6 revisions

Atomic states

The most basic states, these are leafs in our state chart and can be transitioned to and from.

In the following example both On and Off are atomic states.

final machine = StateMachine.create(
  (g) => g
    ..initial<Off>()
    ..state<Off>(builder: (g) => g..on<OnToggle, On>())
    ..state<On>(builder: (g) => g..on<OnToggle, Off>())
);

Terminal states

Conceptually known as "Final" states, these are states to which a state machine cant transition from.

In the following example Dead is a terminal state.

final machine = StateMachine.create(
  (g) => g
    ..initial<Alive>()
    ..state<Alive>(builder: (g) => g..on<OnDie, Dead>())
    ..state<Dead>(type: StateNodeType.terminal)
);

Compound states

Compound states are not leafs, they contain children nodes and whenever a compound state is active, one of it's child nodes is also active.

In the following example, Alive is a compound state composed by Young, MiddleAged and Old child states.

final machine = StateMachine.create(
  (g) => g
    ..initial<Alive>()
    ..state<Alive>(
      builder: (b) => b
        ..initial<Young>()

        ..on<OnBirthday, Young>(...)
        ..on<OnBirthday, MiddleAged>(...)
        ..on<OnBirthday, Old>(...)
        ..on<OnDeath, Purgatory>()

        ..state<Young>()
        ..state<MiddleAged>()
        ..state<Old>(),
    )
    ..state<Dead>(),
);

Parallel states

Conceptually known as "Orthogonal regions". Whenever a parallel state is active, all it's children are active as well.

In the following example both, MainKeypad and NumericKeypad are active.

final machine = StateMachine.create(
  (g) => g
    ..initial<Keyboard>()
    ..state<Keyboard>(
      builder: (b) => b
        ..state<MainKeypad>(
          builder: (b) => b
            ..initial<Default>()
            ..state<Default>((b) => b..on<ToggleCaps, CapsOn>())
            ..state<CapsOn>((b) => b..on<ToggleCaps, Default>()),
        )
        ..state<NumericKeypad>(
          builder: (b) => b
            ..initial<Numbers>()
            ..state<Numbers>((b) => b..on<ToggleCaps, NumLock>())
            ..state<Arrows>((b) => b..on<ToggleCaps, NumLock>()),
        ),
    ),
);

Initial state

This element represents the default initial state for a complex element (i.e. one containing child or elements.

In the following example the machine will start with Alive > Young as initial state. And if we transition from Dead to Alive, the machine will automatically enter the Young state.

final machine = StateMachine.create(
  (g) => g
    ..initial<Alive>()
    ..state<Alive>(
      builder: (b) => b
        ..initial<Young>()

        ..state<Young>()
        ..state<MiddleAged>()
        ..state<Old>(),
    )
    ..state<Dead>(
      builder: (b) => b..on<Resurrect, Alive>()
    ),
);

Check state

To see if your machine is in a given state, you can either use isInState(Type) or matchesStatePath(List<Type>).

Usually the isInState is enough, you'll only need matchesStatePath if you have states with the same type under different parallel machines.

In the following example you have the states _Walk, _Wait and _Stop under both _Crosswalk1 and _Crosswalk1.

final machine = StateMachine.create(
  (g) => g
    ..initial<_Green>()
    ..state<_Green>(
      builder: (b) => b..on<_OnTimer, _Yellow>(),
    )
    ..state<_Yellow>(
      builder: (b) => b..on<_OnTimer, _Red>(),
    )
    ..state<_Red>(
      type: StateNodeType.parallel,
      builder: (b) => b
        ..state<_Crosswalk1>(
          builder: (b) => b
            ..initial<_Walk>()
            ..state<_Walk>(
              builder: (b) => b..on<_OnPedWait, _Wait>(),
            )
            ..state<_Wait>(
              builder: (b) => b..on<_OnPedStop, _Stop>(),
            )
            ..state<_Stop>(
              type: StateNodeType.terminal,
            )
        )
        ..state<_Crosswalk2>(
          builder: (b) => b
            ..initial<_Walk>()
            ..state<_Walk>(
              builder: (b) => b..on<_OnPedWait, _Wait>(),
            )
            ..state<_Wait>(
              builder: (b) => b..on<_OnPedStop, _Stop>(),
            )
            ..state<_Stop>(
              type: StateNodeType.terminal
            )
        )
        ..onDone(
          type: StateNodeType.terminal,
          actions: [(_) => actions.prepareGreenLight()],
        ),
    )
);

If you use isInState it will return true if any of these crosswalks is in that state, but if you want to be more specific you'll have to use matchesStatePath. To use matchesStatePath you need to pass a List of States and they are checked against the path of each active node.

// Match a unique state
finalMachine.isInState(_Red)

// Match states by their path.
finalMachine.matchesStatePath([_Crosswalk1, _Walk])
finalMachine.matchesStatePath([_Crosswalk2, _Walk])