state_machine 2.0.4

  • README.md
  • CHANGELOG.md
  • Installing
  • Versions
  • 76

Dart State Machine

Pub Build Status codecov.io documentation

Easily create a finite state machine and define legal state transitions. Listen to state entrances, departures, and transitions.

Getting Started

Import the state_machine package:

import 'package:state_machine/state_machine.dart';

Create a Machine

Once created, the StateMachine will be used to create states and state transitions.

StateMachine light = new StateMachine('light');

Define a Set of States

Use the machine to create all required states. A string name is required for ease of debugging.

State isOn = light.newState('on');
State isOff = light.newState('off');

It's recommended that states be named in the format "is[State]". This may seem strange at first, but it has two main benefits:

  1. It helps differentiate states from transitions, which can be confusing since many words in English are the same as a verb and an adjective ("open" or "secure", for example).
  2. It reads better when calling the state to determine if it's active, as will be demonstrated later.

By defining legal state transitions, you can prevent certain actions based on the current state of the machine. Defining a state transition requires a name (again for ease of debugging), a list of valid "from" states, and the state to transition the machine to.

StateTransition turnOn = light.newStateTransition('turnOn', [isOff], isOn);
StateTransition turnOff = light.newStateTransition('turnOff', [isOn], isOff);

Start the Machine

Before executing any state transitions, the machine should be started at a specific starting state.

light.start(isOff);

Executing a State Transition

The StateTransition class implements Function so that you can simply call a transition to execute it.

light.start(isOff);
turnOn(); // transitions machine from "isOff" to "isOn"

Determining the Active State

The StateMachine instance exposes a current state property which allows you to retrieve the machine's current state at any time.

light.start(isOff);
light.current == isOff; // true

Additionally, the State class implements Function so that you can simply call a state to determine if it's active.

light.start(isOff);
isOff(); // true
isOn();  // false

Listening to State Transitions

The StateTransition class exposes a listen() method that allows you to listen to the transition and receive an event every time the transition executes.

turnOn.listen((StateChange change) {
  print('Light transitioned from ${change.from.name} to ${change.to.name}');
});
light.start(isOff);
turnOn(); // "Light transitioned from off to on"

Passing Data with a State Transition

State transitions accept an optional payload in case you need to pass data along to listeners.

turnOn.listen((StateChange change) {
  print('Light turned on. Wattage: ${change.payload}');
});
light.start(isOff);
turnOn('15w'); // "Light turned on. Wattage: 15w"

Listening for State Entrances and Departures

The State class exposes two streams so that you can listen for the state being entered and the state being left.

isOff.onLeave.listen((StateChange change) {
  print('Left: off');
});
isOn.onEnter.listen((StateChange change) {
  print('Entered: on');
});
light.start(isOff);
turnOn(); // "Left: off"
          // "Entered: on"

Wildcard State and State Transitions

The State class exposes a static instance State.any that can be used as a wildcard when defining a state transition.

StateMachine machine = new StateMachine('machine');
State isFailed = machine.newState('failed');

// This transition will be valid regardless of which state the machine is in.
StateTransition fail = machine.newStateTransition('fail', [State.any], isFailed);

Illegal State Transitions

When you create state transitions, you must define the list of valid "from" states. The machine must be in one of these states in order to execute the transition. If that's not the case, an IllegalStateTransition exception will be thrown.

// Consider a door with the following states and transitions.
StateMachine door = new StateMachine('door');

State isOpen = door.newState('open');
State isClosed = door.newState('closed');
State isLocked = door.newState('locked');

StateTransition open = door.newStateTransition('open', [isClosed], isOpen);
StateTransition close = door.newStateTransition('close', [isOpen], isClosed);
StateTransition lock = door.newStateTransition('lock', [isClosed], isLocked);
StateTransition unlock = door.newStateTransition('unlock', [isLocked], isClosed);

// Let's transition the door from open, to closed, to locked.
door.start(isOpen);
close();
lock();

// In order to open the door, we must first unlock it.
// If we try to open it first, an exception will be thrown.
open(); // throws IllegalStateTransition

Canceling State Transitions

State machines have a set of legal state transitions that are set in stone and provide the required structure. But, there may be scenarios where a state transition may or may not be desirable based on additional logic. To handle this, state transitions support cancellation conditions.

// Consider two state machines - a person and a door.
// The door can be locked or unlocked and the person
// can be with or without a key.
StateMachine door = new StateMachine('door');
State isLocked = door.newState('locked');
State isUnlocked = door.newState('unlocked');
StateTransition unlock = door.newStateTransition('unlock', [isLocked], isUnlocked);

StateMachine person = new StateMachine('person');
State isWithKey = person.newState('withKey');
State isWithoutKey = person.newState('withoutKey');
StateTransition obtainKey = person.newStateTransition('obtainKey', [isWithoutKey], isWithKey);

door.start(isLocked);
person.start(isWithoutKey);

// Add a cancellation condition for unlocking the door:
// If the person is without a key, cancel the unlock transition.
unlock.cancelIf((StateChange change) => isWithoutKey());

unlock(); // false (canceled)
isUnlocked(); // false
obtainKey();
unlock(); // true (not canceled)
isUnlocked(); // true

Development

This project leverages the dart_dev package for most of its tooling needs, including static analysis, code formatting, running tests, collecting coverage, and serving examples. Check out the dart_dev readme for more information.

Changelog

1.0.0

  • Initial version of state_machine.

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  state_machine: ^2.0.4

2. Install it

You can install packages from the command line:

with pub:


$ pub get

with Flutter:


$ flutter packages get

Alternatively, your editor might support pub get or flutter packages get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:state_machine/state_machine.dart';
  
Version Uploaded Documentation Archive
2.0.4 Oct 11, 2018 Go to the documentation of state_machine 2.0.4 Download state_machine 2.0.4 archive
2.0.3 Oct 11, 2018 Go to the documentation of state_machine 2.0.3 Download state_machine 2.0.3 archive
2.0.2 Sep 18, 2017 Go to the documentation of state_machine 2.0.2 Download state_machine 2.0.2 archive
2.0.1 Mar 15, 2017 Go to the documentation of state_machine 2.0.1 Download state_machine 2.0.1 archive
2.0.0 Jan 5, 2016 Go to the documentation of state_machine 2.0.0 Download state_machine 2.0.0 archive
1.0.1 Jul 7, 2015 Go to the documentation of state_machine 1.0.1 Download state_machine 1.0.1 archive
Popularity:
Describes how popular the package is relative to other packages. [more]
53
Health:
Code health derived from static analysis. [more]
99
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
100
Overall:
Weighted score of the above. [more]
76
Learn more about scoring.

We analyzed this package on Oct 11, 2018, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.0.0
  • pana: 0.12.4

Platforms

Detected platforms: Flutter, web, other

No platform restriction found in primary library package:state_machine/state_machine.dart.

Health suggestions

Fix lib/src/state_machine.dart. (-1 points)

Analysis of lib/src/state_machine.dart reported 2 hints:

line 107 col 5: 'manageStreamSubscription' is deprecated and shouldn't be used.

line 435 col 5: 'manageStreamSubscription' is deprecated and shouldn't be used.

Maintenance suggestions

Maintain an example.

None of the files in your example/ directory matches a known example patterns. Common file name patterns include: main.dart, example.dart or you could also use state_machine.dart.

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=1.24.3<3.0.0
w_common ^1.15.0 1.15.0
Transitive dependencies
intl 0.15.7
logging 0.11.3+2
meta 1.1.6
path 1.6.2
Dev dependencies
build_runner >=0.6.0 <1.0.0
build_test >=0.9.0 <1.0.0
build_web_compilers >=0.2.0 <1.0.0
coverage >=0.10.0 <0.13.0
dart_dev ^2.0.0
dart_style ^1.0.0
test >=0.12.30 <2.0.0