Widget-Perfect State Management in Flutter! Is it Possible?

MELLATI Fatah
Flutter Community
Published in
10 min readMay 25, 2019

If it happened that you read an introductory section about Flutter, you might have faced statements like “Flutter is a toolkit offered by Google to build cross-platform mobile, desktop and web applications from one source codebase”. Most likely, you have not missed the statement either that it is all widgets. It probably hit you too, that Flutter owns every pixel on the screen which allows it to be “Pixel Perfect”.

That is all nice and good but when it comes to state management in Flutter opinions diverge. Can any of the available state management techniques be qualified as the most optimal? I say optimal in the sense of easiness of implementation without losing the ability to surgically rebuild the widget tree whenever you want. Let’s find out together!

In the Google I/O 2019 event, the Flutter team took a choice and went with the Provider package as the recommended approach for state management. The good thing about this announcement is that Provider is a community-driven package. This reflects how open the Flutter team towards community contributions is. This announcement is not only a recognition to the creator of the Provide alone, yet also a message to all the community members to encourage them to try to found their solutions. In this article, I will present one contributional effort to make state management in Flutter as easy and as effective as possible.

states_rebuilder:

The states_rebuilder package is based on the principle of separation of concerns. It is a design principle for separating your application into distinct sections so that each section addresses a separate concern. In this case, you define a logic class to hold all of your application logic and provide this class to your User Interface (UI) to make it accessible from all your widgets.

Providing BloCs:

The first Widget offered by states_rebuilder package is BlocProvider whose sole role is to make an instance of MainBloc, for our example, available to the RoutPage and all its child widgets so that they can share data using the InheritedWidget behind the scene.

Prepare the scene:

The UI consists of a simple grid of 12 items. In the BloC, there is a counter variable and an increment() method to increment the counter.

The UI part before reactivity
The logic of the app
Simple 3 x 4 grid displaying the value of the counter

Our app is stateless and it is not reactive. Let’s use the strength of states_rebuilder to add reactivity.

Rebuild all:

The second widget offered by the states_rebuilder package is the StateBuilder.

Wrap what you want to make reactive with StateBuilder and define the two required parameters

Wrap what you want to make reactive with StateBuilder and define at least two required parameters:

1- blocs: a list of instances of your logic classes you want the StateBuilderto subscribe to the list of listeners;
2- builder: a closure with two parameters, the first is of BuildContext type that holds the context of this widget, and the second of String type and it holds the identity (or the key or the address) of this widget. As we do not need them now,we just used underscores.We will use them late on in this article.

In the Bloc we use rebuildStates() to notify listeners subscribed in this particular BLoC to rebuild.

That all, when you tap on any of the boxes all of them rebuild.

rebuildState() without parameter rebuilds all the subscribed listeners

If you’re familiar with other libraries such as scoped_model and provider, we probably noticed that they achieve a similar behavior using notifyListeners. What makes states_rebuilder different is the fact that it gives you more capabilities to surgically rebuild the widget tree. Continue reading this article and I think you’ll be pleasantly surprised.

Rebuild one:

If you want to be widget perfect, you want to rebuild only the tapped box. This easily achieved with states_rebuilder:

Rebuild the tapped box. Changes are highlighted

All the change you have to do on the UI are highlighted. Not much!
The second parameter of the builder closure is of type String. It stores the unique identity of the state (the address of the state). I choose to name it tagID, but it can be named tagMe, thisTag or any other name you think it is more descriptive. Just like the first parameter of the builder closure which you can name it context, or simple “c” or just an underscore if not used.
the logic class becomes:

When rebuildStates is called with a list of tags. StateBuilder with those tags will rebuild

This is how the app looks like after these tiny changes:

Rebuild the tapped box. the second parameter of the builder closure is a String that holds the address of the tapped box.

Please note that all boxes hold the same value of the counter, but only the tapped one is rebuilt to reflect the updated value.

You see who easy it is, how efficient it is, how widget perfect it is!

Rebuild a set:

Let’s say you want to rebuild a set of boxes that have common criteria. In our example,let’s suppose that we want that all widgets that have an even index to rebuild all together. Also, we want the same thing to widgets that have an odd index.

The fact that we want to rebuild a specific set of boxes leads us to distinguish them form other boxes. This is achieved by giving them a name or tagging them so that when reBuildStates is called with the chosen tag, those widgets that have this tag will rebuild. The tag parameter in the states_rebuilder is dedicated to such a use.
This is how the UI looks like:

tag can be any type of date. It is used to filter the list of widgets to rebuild when calling statesRebuilder

the bloc part remains the same.

the parameter tag is dynamic so that it accepts any type of data.

All widget with the same tag will be notified to rebuild when rebuidStates is called with this tag

Rebuild remote widget:

To show more functionality, let’s say we want to add a widget to show if the tapped widget has even or odd index.
This is one possible implementation:
The UI part:

Adding an Icon to show if index of the tapped box is even or odd. a circular progress bar is shown if no box is tapped

The bloc part:

rebuildStates take al list of dynamic tags

The solution is to use a StateBuilderwith a tag. We can choose a simple String to name our tag, but for a big project, it is preferred to use an enums so that we can make full profit of our preferred IDE helping functionality such as auto-completion, peek and show definition and references. The conventional place to declare your enums is in the same file where your BloC classes are. For a bigger project, it is even more helpful if you declare your enums in one file and import it when needed.

You can build any widget by given it a specif tag

A full functional statefulWidget:

StateBuilderoffers many other parameters to mimic all the functionality of a StatefullWidget:

StateBuilder(
{Key key,
dynamic tag,
List<StatesRebuilder> blocs,
(BuildContext, String) → Widget builder,
(BuildContext, String) → void initState,
(BuildContext, String) → void dispose,
(BuildContext, String) → void didChangeDependencies,
(BuildContext, String, _StateBuilder) → void didUpdateWidget
})

StateBuilder with mixins:

Are we saying goodbye to StatefulWidget? NO, because the state class of a StatefulWidget can be mixin with other class to support animation, tabView and many other things.
states_rebuilder offers the StateWithMixinBuilderwidget to handle some of the most commonly used mixins.

StateWithMixinBuilder<T>(
{Key key,
dynamic tag,
List<StatesRebuilder> blocs,
(BuildContext, String) → Widget builder,
(BuildContext, String, T) → void initState,
(BuildContext, String, T) → void dispose,
(BuildContext, String, T) → void didChangeDependencies,
(BuildContext,String, _StateBuilder, T) → void didUpdateWidget,
(String, AppLifecycleState) → void didChangeAppLifecycleState
MixinWith mixinWith
})

Mixin supported until now are singleTickerProviderStateMixin, tickerProviderStateMixin, AutomaticKeepAliveClientMixin and WidgetsBindingObserver.

Animation Example:

I won’t go through the details on how to use animation in Flutter. The aim of the example is to show you a practical case of using StateWithMixinBuilder.

If you want more details on how to set your animation, this is a good article about animation written by Raouf Rahiche. (link)

The UI is:

StateWithMixinBuilder is used to add some of common mixins support

The bloc is:

A typical animation setup

From the initState() of the StateWithMixinBuilder we call the initAnimation from the bloc with TickerProvider type parameter. It is here where all the conventional animation setup occurs.
In the triggerAnimation() of the bloc we added rebuildStates() as a listener, which means that all the boxes will rotate when any of them is tapped.

With one animation controller we can animate as many widgets as we want

To rotate only one box, we follow the same steps mentioned above.

The change in UI part.

The change in the bloc part

the result.

Rotating the tapped box. without removing the previous listener.

The last tapped box will continue to animate because listeners are not removed. Here is the bloc with removing listeners:

The result:

Animating the tapped box after removing the last listener

To animation the even and the odd indexed boxes, there is no special thing, as mentioned in the steps above:

The change in the UI are:

The result is:

Animating even indexed boxes when an even box is tapped. The same for odd indexed boxes

Based on the states_rebuilder, I created a library dedicated to the animation called the animator.

This is an article explaining some of its functionalities.

AppLifecycleState Example:

This is an example using AppLifecycleState to track the state of the app (onPause(), onResume() in Android).

The UI part:

The Bloc part, we add lifecycleState method:

The result is:

Performance consideration:

As states_rebuilder rely on the speed of fetching data form Maps where listeners are stored, performance questions may arise.
I try to compare the time taken to execute the rebuildState() function with that taken by a simple print statement.

I followed this approach:

measure the execution time of rebuildState() on microseconds and compare it with print() statement

This is the result in the debug mode:

rebuildStates() for 12 grids VS print() results

The states_rebuild average is 50 microseconds and the print statement 20 microseconds in the debug mode.

I increased the number of grids to 500, the results are

rebuildStates() for 500 grids VS print() results

This means that the average time taken to mark 250 widgets as to need to rebuild is 150 microseconds in debug mode. IT is important to note that states_rebuilder stores only listeners which are mounted. When a state is disposed, its listener is removed. In most common situation states_rebuilder stores about tenths of listeners.

I used the same technique with notifyListener() used in scoped_model and provider, I found:

average time taken to execute notifyListeners is 50 microseconds

This is one contribution from the community to manage the states in Flutter. I hope we like it.

states_rebuilderr for the web:

The good news is that states_rebuilder works in the web just as well as it does in the mobile. This GIF shows all the above example with the same code in web browser:

states_rebuilder in the wab.

The full code of this article is here.

The page of the states_rebuilder is here.

Happy to hear your claps, to read your comments, to react to your critics, to see your stars in GitHub and above all happy to be part of the great Flutter community.

--

--