I started working on implementing a state machine.
It worked great. But that was not enough. How can I make it usable?
I ended up creating a state management library. Here is how 👇
Tech stack
- dart: the state machine is implemented in plain dart (with some wizardy around generic types and type safety)
- Flutter: the state machine was working with dart, I then wrote some code to use it with Flutter as well
Setup
Creating a new package in dart is just one command away:
dart create -t package my_new_package
I started there. Nothing more nothing less. After all this is plain dart.
Get started
Now, initially my intention was making a state machine.
I already knew well enough the theory behind state machines, and I also had my fair bit of practice with them (XState 👈).
What I didn't expected was my detour into state management,
Stream
,Provider
, and how to connect state changes with a Flutter widget.
This was all a practical experiment, how to connect all the pieces together in a working library.
Let's dive into the implementation!
There is more 🤩
Timeless coding principles, practices, and tools that make a difference, regardless of your language or framework, delivered in your inbox every week.
Implementation
As the project evolved we can pinpoint 3 distinct phases 👇
Implementing a State Machine
Phase 1: State Machine.
Main challenge: how to model a type safe, usable, and flexible state machine?
The type safe part was especially important. This requires some tricky code around abstract class
, generic types, and nullable/optional parameters.
abstract class State {
void entry() {}
void exit() {}
}
/// A [StateEvent] extends [State] and also has a generic [S] that extends [State] as well 🔄
abstract class StateEvent<S extends State> extends State {
Map<Event, S> get events => {};
S? next(Event event) => events[event];
}
State machines are not enough, entering Statecharts
A state machines has a final list of states and events to transition between states. Not enough in practice.
Welcome Statecharts!
A statechart expands over a state machine to add actions, context, concurrency, and much more.
This makes a state machine more useful in practice.
I added another generic parameter, with a (full) new refactoring of states and events:
abstract class Machine<Context, S extends State<Context>, E extends Event<Context>>
It works. How about streaming state in Flutter?
The machine was working great. But it was not of much use in practice.
How can I take this thing to the next level? How about an integration with Flutter?
Now, I never had some real practice using Stream
in dart. This step required some serious engineering genius.
Solution: Copy-paste everything from the
bloc
package code, try to understand it, and make it work for my machine.
I copied everything from the bloc package with the goal of understanding how to stream states
This is why open source is awesome! I copied all the code from bloc
, and gradually removed features until I reached the core of how streaming works.
As you may have guessed, it worked!
I then did the same with flutter_bloc
. At the end I created a state management library based on Provider
that listens and streams states changes from a state machine.
class MachineBuilder<M extends StateStreamable<Context, S>, Context,
S extends State<Context>> extends MachineBuilderBase<M, Context, S> {
const MachineBuilder({
required this.builder,
Key? key,
M? machine,
}) : super(key: key, machine: machine);
final MachineWidgetBuilder<Context, S> builder;
@override
Widget build(BuildContext context, Context ctx, S state) =>
builder(context, ctx, state);
}
👉 For all the details and code snippets you can read the full article containing all the details of the implementation.
Takeaways
- State machines (Statecharts) are awesome for modeling and managing state
- Less theory and more practice: when you want to learn a new API dive deep into existing open source code, it will teach you way more than just watching Youtube videos
- State management in Flutter is easy: a class has some internal state and uses a
Stream
to push out state updates, Flutter listens and updates the widget. Nothing more than that - Dart's type system is powerful in its own way: the state machine is completely type safe
At the end I have a working open source prototype. I don't intend to expand and publish this as a package on pub.dev.
Since it's open source anyone can pick it up and continue the work.
See you next 👋