Create a Flutter state management library with 100 lines of code

Xianzhe Liang
6 min readJun 26, 2022

--

State management shouldn’t be that complex. Create a simple and powerful library yourself with only 100 lines of code.

State management is a hot topic in the Flutter community. In fact, according to a Reddit thread, there are 42 libraries available for you to choose from! 42, it means “we have reached the optimal amount”.

It is no surprise that each library has its fan. The most popular ones are all great solutions. However, it is hard for beginners to decide which one is the best for their project.

In this post, let’s create our own state library from scratch. It is simple and without any magic. You will be surprised to see how easy it is. It can also help you understand how other state management solutions work.

With only 100 lines of code, we will get a fully reactive state management solution. With it, we can build counter/todo apps, change the app theme dynamically, and much more. Isn’t that amazing?

If you ask what’s the single most important thing about a state management library, that would be how it propagates state changes.

Some solutions propagate state changes using Stream (e.g. flutter_bloc, flutter_redux, getx), some rely on Flutter widget features (e.g. provider), and some use a customized subscription model (e.g. riverpod, mobx).

Think about a counter app, what do we really need?

  • We need something to hold an integer.
  • If the integer changes, change a widget from Text(“0”) to Text(“1”).

If this picture appears in your mind, great, you capture the most important thing here: dependency.

What’s the simplest way to model dependencies? A dependency graph!

  • Nodes can hold states, which can be integers, strings, objects, or Widgets.
  • Edges indicate how the state change should flow.

For a counter app, we need a Node<int> and a Node<Widget>, as shown in the above graph.

But how do we build our library from here?

Let’s call our node Creator and our graph Ref (well, you can totally name them differently). This is the graph Ref:

How do we define the node Creator? A creator needs to depend on other creators’ states, and we can do that by passing Ref to creators.

To avoid users mutating the creator’s state directly, let’s make the creator immutable and use another private object Element to hold state:

With these, the counter app’s two nodes will be:

Here we establish the dependency counter -> text through Ref.watch, which we will implement shortly. A creator can depend on multiple other creators this way.

Next, change Ref to use Element to hold states:

Next, let’s add APIs to change and propagate the creator’s state:

Don’t forget to implement Element’s recreate function:

Great! We now have a dependency graph that can propagate state changes!

Let’s verify it works, play with the code in DartPad here:

In this section, let’s connect our dependency graph to Flutter widgets. There are three things we need to achieve:

  1. Allow widgets to access the graph.

2. Update the widget if its dependency’s state changes.

3. Automatically dispose creators which are not needed anymore.

Let’s do it!

  1. Allow widgets to access the graph.

This is easy if you know InheritedWidget:

Now wrap your app in a CreatorGraph, then you can access Ref through context.ref:

2. Update the widget if its dependency state changes.

Remember we need a Creator<Widet> for our counter app? Let’s put it inside a StatefulWidget. If the dependency state changes, simply setState and allow Flutter to do the work:

3. Automatically dispose creators which are not needed anymore.

The above code calls ref.dispose when the widget is disposed. We also need to dispose the creator’s dependencies if they are not needed anymore:

Amazing! We are all done here. Let’s build our counter app! Play with it in DartPard here:

Even though the package we built seems simple, it is fully functional and can achieve a lot of things elegantly.

A few examples are below. Try to change the theme color dynamically or build a Todo app yourself.

  • Counter app (DartPad)
  • Counter app with increment/decrement API (DartPad)
  • Weather app with async backend call (DartPad)
  • News app with an infinite list of news and loading indicator (DartPad)

Hope you enjoyed reading this doc so far. There are still a few things needed in order to use this library in production. But you are 70% there.

Features we want to add:

  • Remove self from syntax. Creator((ref, self) => ref.watch(number, self) * 2)should be Creator((ref) => ref.watch(number) * 2), which is safer and more readable.
  • Allow dynamic dependency like this:
final C = Creator((ref) {
final value = ref.watch(A);
return value >= 0 ? value : ref.watch(B);
});
  • Make Creator<Future<T>> works better. Right now, even if the creator generates the same T, Future<T> is considered as a state change.

We also need to productionize a few pieces:

  • CreatorGraph should dispose the whole Ref when it is disposed.
  • Watcher has a few redundant build calls right now.
  • Need logging, error handling, testing, etc.

So what should you do now? Please kindly wait for the next article…

… or even better, read the final result here!

https://github.com/terryl1900/creator

Sorry for not telling you earlier, I’m the author of this newly released state management library. This article shows how I thought about state management and built the first version of the library.

The core library is only 500 lines of code. If you have read this article, you should be able to read its source code pretty fast. Give it a try and let me know what you think!

And after that, pick a few state management libraries and scan their source code. Just try to find out: where are states stored and how are states propagated.

Have fun becoming an expert!

Xianzhe from https://chooly.app

--

--