We already have a huge bunch of options for managing our app state in Flutter, so you could wonder why spend some time on another one?
The reason is simple: I love creating things, I can’t stop. Sometimes I find myself thinking about what I could make when I rock my daughter to sleep. Sometimes it can be useful, other times it’s not but I still have learned something in the end.
Today I want to tell you the story of Maestro and you will decide whether it can help you or not. If it does, it will be music to my ears 🎵!
All of this started after using two packages I like a lot: Provider and Freezed (kudos to Remi Rousselet if you’re reading this 😉). I use Freezed to generate the classes of the data coming from the APIs and Provider +
ChangeNotifier for managing my app state. It’s great but the mutability of
ChangeNotifier bother me. So I started to think about a clear separation between the app state and the components that perform actions on it, but I didn’t want to end up with something too verbose either. One night before going to bed, I opened VSCode created a new Flutter project and started to prototype something. In only 300 lines of code I ended up with something I liked and found pleasant to use. I like to share, so instead of keeping this to myself I decided to create a new package I called Maestro and here we are 😅!
🏗 A global app architecture
Before going further on the package let me show you how I separate an application in three layers, each one built upon the previous one:
Many of you may already be familiar with this kind of separation, if not this is a little explanation of each layer:
This layer is responsible for fetching and manipulate the data from one or more sources.
There are two main components in this block:
Data sources which are providers of raw data (like a web service client)
Repositories that use one or more data sources to provide data understandable by the rest of the app (we can call them models).
This layer is the glue between the UI layer and the Data layer. It takes orders given by the user input and then communicates with repositories in order to modify a part of the app state the UI will display.
This layer is responsible for providing something to the user so that they can interact with the data the app produces and consumes.
🗂 App state management with Maestro
In Flutter, you structure your app with widgets. A widget is a declaration of something in your app, it can be a visual element, a way to access your data, just a log writer and all that jazz. Remember: Everything is a widget! Therefore in my vision, the data sources, the repositories and the objects in the logic layer should be encapsulated in widgets. In Maestro the widget responsible for storing these objects is called…
Let’s say you want to expose the current user to a subtree, with Maestro you’ll just have to write this:
If you want to expose the current user and the current cart, you can nest the
If you have a lot of elements to expose, it’s more convenient to use another widget called
Maestros. This widget takes a list as parameter and recreate a nested tree of
In any widget built by
SubTree you will be able to read and write the current user and the current cart. For example you may use
Maestro.read<Cart>(context); to read the current cart and
Maestro.write(context, User(firstName: ‘Anakin’, lastName: ‘Skywalker’)); to write the current user. Like Provider, you need to provide a
BuildContext to these methods.
With Maestro, the logic layer is composed essentially by objects I called
Composers. The composers know how to read and write the models declared before them.
Let’s see a simple
A composer is a class which apply the mixin called
Composer. By applying this mixin, you’ll get access to the methods
write that you can use to get and set objects exposed before the composer. As you can see, you don’t need to pass any
BuildContext here, Maestro will do that for you 😉.
This composer depends on a
UserRepository and gets it with a call to the
authenticate method we delegate the authentication to the repository and our job is to handle errors (we don’t do it in the example) and to update the app state with a call to the
These are the few components you need to know to use Maestro for managing the app state.
🎼 Let’s play with Maestro!
This was the theory, now I will show you how to do the Flutter Counter app with Maestro:
We encapsulate the
MaterialApp with a
Maestros widget in which we expose the
Counter with an initial value of 0 and the
CounterComposer. By doing so, the entire app can access the current counter and the composer.
_HomePage you can see a specific widget
_CounterText (we will see it in a few seconds) and the call to the
increment method of the composer when the user taps on the floating action button. You can see that instead of
Maestro.read<CounterComposer>(context) we used an extension method on
BuildContext provided by Maestro.
This widget use another method of Maestro called
listen. This method is able to read the value of the nearest
Maestro ancestor of the given type and tells the Flutter to rebuild this widget when the value changes.
We have other options than creating a dedicated widget in order to listen for changes but this is how I like to do.
Here we have the implementation of the model called
Counter, and its composer called
The model should be immutable, for doing it, we use another great package called Equatable. You can also use Freezed to achieve the same result.
Maestro is for now an experiment. I don’t know yet if there are cases where it does not fit, but I find it very promising. The Fluture will tell 😜!
Let me know what you think about it.