reactive_state_machine
offers a flexible and user-friendly approach to creating reactive hierarchical finite state machines. It's written in pure Dart.
Note: This package is not yet production-ready. APIs may changes in future version.
This package introduces two types of StateMachines. Both are hierarchical finite state machines, but they differ in how states and transitions are defined and used. Both implement the StateMachineBase
interface.
EventStateMachine
is a state machine that receives input from an event bus. Events are processed based on the current state and may trigger side effects or state transitions. It works best when state machine should react to a stream or when you need to manage multiple event source simultaneously.
CommandStateMachine
is a state machine interacted with through the invocation of the state's methods. This machine is designed to leverage Dart's pattern matching, enabling compile-time safe interactions by explicitly defining permissible transitions. It works best when the consumer of the state machine want to imperatively interact with it.
Transitions and event processing rely on State and Event types, not on object equality.
State machines don't enforce a specific reactivity API, giving developers flexibility in choosing implementations like Stream
, ChangeNotifier
, BehaviorSubject
, and more.
This package includes a Dart Stream
-based implementation for both machine types: StreamEventStateMachine
and StreamCommandStateMachine
.
The flutter_reactive_state_machine
package provides additional implementations for Flutter's standard Listenable
, along with other utilities tailored for Flutter.
EventStateMachine
sealed class State {
const State();
}
class Green extends State {
const Green();
}
class Red extends State {
const Red();
}
class Orange extends State {
const Orange();
}
sealed class Event {
const Event();
}
class TimerFinished extends Event {
const TimerFinished();
}
final class TraficLight extends StreamEventStateMachine<State, Event> {
TraficLight(super.initial);
@override
late final states = {
// Green state
define<Green>(($) => $
..onEnter(_startTimer)
..on<TimerFinished>((state, event) => const Orange())),
// Orange state
define<Orange>(($) => $
..onEnter(_startTimer)
..on<TimerFinished>((state, event) => const Red())),
// Red state
define<Red>(($) => $
..onEnter(_startTimer)
..on<TimerFinished>((state, event) => const Green())),
};
void _startTimer(_) {
Timer(Duration(seconds: 1), () => add(const TimerFinished()));
}
}
CommandStateMachine
sealed class UserState with State<UserModel, UserState> {}
class Unknown extends UserState {
@override
Future<void> onEnter() async {
final user = await machine.authenticationService.currentUser();
if (user != null) {
transitionTo(Authenticated(user));
} else {
transitionTo(Unauthenticated());
}
}
}
class Unauthenticated extends UserState {
Future<bool> login() async {
final user = await machine.authenticationService.tryLogin();
if (user != null) {
transitionTo(Authenticated(user));
return true;
} else {
return false;
}
}
}
class Authenticated extends UserState {
Authenticated(this.user);
final User user;
Future<void> logout() async {
await machine.authenticationService.logout();
transitionTo(Unauthenticated());
}
}
final class UserModel extends StreamCommandStateMachine<UserState> {
UserModel({
required this.authenticationService,
}) : super(Unknown());
final AuthenticationService authenticationService;
}
Usage
void main() async {
final machine = MyStateMachine();
if (machine.state case SomeState state) {
state.yourData;
}
machine.ifState((TestState state) => state.doSomething());
final content = switch (machine.state) {
StateA() || StateB() => "A or B",
StateC() => "C",
};
}