Flutter State Management has Never Been Easier. Think Statelessly then add Reactivity

For those who are familiar with Flutter and are in a hurry, please bear with me for a couple of lines. I want show you something interesting:

Let us define a stateless widget that renders a Text to display a counter value and a Floating Action Button (FAB) to increment it:

main.dart

The logic component is defined in the MainBloc class:

main_bloc.dart _ I use the term BloC loosely. There are no Streams here

With only this bit of code, WHEN FAB IS CLICKED nothing will change in the UI, for the simple reason: widgets are stateless and our app is said to be none reactive or interactive.

Please stay with me for more lines. Now, look at this

main.dart

The MainBloc class:

main_bloc.dart

With only the highlighted in the pictures above, when the FAB is clicked only the text widget that is wrapped inside the StateBuilder is rebuilt, showing the updated counter value.

That’s cool, isn’t it? In the remainder of the article, I will show how StateBuilder works behind the scenes and will also demonstrate that no matter how nested the widgets are, and how many BloCs there are, it is very easy to control the state of even the tiniest widget from any BloC.

End of the advertising part. Let me start my article with the usual introduction section.

Introduction:

In my first article, I proposed a new approach to manage the states of the app. The idea there is simple, it revolves around a helper class BloCSetting that allow us to call setState() method from the bloc to rebuild any widgets we want to be updated after state mutation.

Using this approach, we can easily achieve total separation of business logic from UI presentation. One side effects of the proposed approach is the extensive use of Stateful widgets which clutters the UI with lots of boilerplate. My first thought is to introduce a class that allows me to create stateful widget from stateless one. I initially decided to call this class StatefulBuilder before discovering that the default flutter framework has one, but unfortunately it does not do what I expected from it. So I decided to create new one and name it StateBuilder with this constructor:

StateBuilder( {
 Key key
 String stateID
 List<BloCSetting> blocs
 (State) → Widget builder
 (State) → void initState
 (State) → void dispose
 (State) → void didChangeDependencies
 (StateBuilder, State) → void didUpdateWidget
});

Think Statelessly then add Reactivity:

To use the StateBuilder class efficiently, I propose to follow this two stage pattern:

Stage one : Think statelessly.

Your coding journey starts by creating your app files separating presentation UIs from logic BloCs. Provide your BloCs to the UIs. Start coding using only StatelessWidgets. Do not worry about reactivity or interactivity at this moment. Let your concern to be only on how to design a beautiful app that will astonish your potential users.

Stage two : Add reactivity

For any widget you want to make it reactive so to be rebuilt, from the BloCs, after any chunk of logic execution, do the following:

  1. Wrap it inside StateBuilder widget and put it inside the callback of the builder parameter;
  2. give it an id (stateID);
  3. list the blocs you want to be able to update the state from (blocs);
  4. In case you want to execute some code depending on the life-cycle of the widget, use it with the help of the parameters : initState, dispose, didChangeDependencies or didUpdateWidget;
  5. Call the rebuildWidgets() method from the bloc with the given id (stateID).

I will practice the above stages with the help of the following examples. I will refer to stage one with the stamp BEFORE (before interactivity) and to stage tow with AFTER (after interactivity).

TextField example:

Stage one: Our demo app consists of a Column inside it two widgets : TextField and a Text. we want the Text widget to display what is typed in the TextField:

main.dart
main_bloc.dart

Stage two: To make the app reactive, we wrap it inside StateBuilder and give it an id=”Text”, and define the blocs where the state will be available :

main.dart — changes are highlighted
main_bloc.dart — changes are highlighted

That is it, with only these changes, the Text widget displays the value that is being typed. It is worth noting that only the Text widget is rebuilt.

We can repeat this pattern for any modification we want to add to our app. for example:

Stage one: Let’s say we want to add an error message of validation under the TextField. with flutter this is an easy task. we update our code to be:

main.dart — showing an error message

the mainBloc becomes:

main_bloc.dart — showing an error message

When typing no error message appears because the TextField widget is not yet reactive.

Stage two: to make TextField reactive we change our code to become:

main.dart — making TextField reactive
main_bloc.dart — making TextField reactive

At this point, I hope you can see the simplicity in making widgets reactive.

Here is a screenshot of the final app

In this particular example, we can wrap the Column widget to make both the TextField and the Text widgets reactive with one StateBuilder and one id. the following code shows how to do this:

main.dart

The corresponding bloc:

main_bloc.dart

The bottom line of this example is the key for writing the UI layer and logic using StatelessWidgets. After you’ve decide which widget needs to be reactive, then just wrap it inside StateBuilder, give it an id and declare the list of blocs from which you want to rebuild the widget with the help of the rebuildWidgets() method.

Fetch and display a list of items example:

Stage one: In this example we will simulate fetching data, displaying a circular progress indicator, and after data is loaded we will display the list of items. For the sake of simplicity the data to be fetched is a list of integers. Also we want to decrements the number of each item when we click on the corresponding tile.

Here how main.dart and the main_bloc.dart look like before making them reactive. The code is easy and self-explanatory.

main.dart — In the title of the listTile I use a random which will be changed when the widgets is rebuild. This is helpful to track which widget is rebuilt
main_bloc.dart

Stage two: to make the app reactive, we have to call fetchList() method and rebuild the widget after data is received. Here are the changes to perform:

main.dart _ Calling fetchList from initState
main_bloc.dart

After wrapping the widget inside the StateBuilder, we called the fetchList() form the initState parameter callback. The fetchList is provided with “state” as a parameter which will be passed to the rebuildWidgets() method to rebuild the widget after awaiting for the Future to resolve and mutating the listISLoading variable to false.

With only these little changes we are able to call the fetchList() method form the initState() and update the widget to display the items list after receiving the items.

We have also the alternative to give the widget an id and declare the blocs list to achieve the same result. To do so, the UI and the bloc become:

main.dart
main_bloc.dart

To make the decrement method active, we perform the following changes:

main.dart
main_bloc.dart

Now if you click on any ListTile, the number in the trailing of the clicked ListTile will decrease and only this ListTile is rebuilt.

Before concluding the section, let us add more complexity to our app. What if we want to remove the ListTile when its count in the trailing reaches zero.

To do so, we are going to only change the bloc class. the UI will remain unchanged. This is the benefit of separation of concerns. the bloc becomes:

main_bloc.dart

Inside the decrement method we check if the item number is greater than zero then only the clicked ListTile will rebuild. for the other case the item is removed from the list and the whole list will be rebuild to remove the corresponding ListTile.

Pay attention to random numbers (item number). It changes when the ListTile is rebuilt

I hope you see that when you follow the proposed Two-Stage pattern, the sole limit to the designer is his imagination. No worry about State lifting neither BloC stream merging nor Redux dispatching and reducing.

If you are interested to see the code of the above examples check out this link.

To further check the validity of the proposed approach, I tried to replicate some of the more complex available demo examples. I’m not going to show how I implemented this, I will be satisfied with some comments.

Filip Hracek — state expirements:

If you follow the code we can notice that I sicked to statelessWidgets with one exception for the CartButton class where I used StatefullWidget. I had to do that because CartButton is animated, and as you know, animated State has to extend SingleTickerProviderStateMixin.

here is a screenshot of some part of the code:

I have another approach that allows me to move all animation setting to the BloC classes and free the UIs from any animation mess which will make it possible to use 100% StatelessWidgets in my app. The implementation of this approach is postponed to another article. Actually, in my plan there are two articles that I thick to titled them:

1- Flutter Animation has Never Been Easier;
2- Flutter Routing has Never Been Easier.

Didier Boelens — movie online catalog

In this example the BloCs are provided using the inheritedWidget.

Here there is something interesting that I want to share with you.(I will not go to the details)

To understand what I will say, please read the original article before.

in the picture above:

(1) is a heart in the MovieDetailsWidget widget;
(2) is a heart in the MovieCardWidget widget; and
(3) is a heart in the FavoriteButton widget.

When you click any card (MovieCardWidget) from the horizontal list, its detail will appear in the detailed container (MovieDetailsWidget) underneath.

To add a movie in the favorite list, you tap on the heart (1) in the figure, a new heart appears on (2) and the number of favorite films increases by 1 (3). to make the heart appears on (2) the whole list should be rebuilt which would be anti-performance.

With the proposed approach, , to to make the heart appear on (2), I can easily manage to get rebuild only the MovieCardWidget widget, that has been tapped to show the detail.

BloCSetting class:

This is the modified version of the BloCSetting class that allow me to do all the good staffs above.

import 'package:flutter/material.dart';
class BloCSetting extends State {
Map<String, State> _stateMap =
{}; //key holds the stateID and the value holds the state
rebuildWidgets(
{VoidCallback setState, List<State> states, List<String> ids}) {
if (states != null) {
states.forEach((s) {
if (s != null && s.mounted) s.setState(setState ?? () {});
});
}
if (ids != null) {
ids.forEach(
(s) {
final State ss = _stateMap[s];
if (ss != null && ss.mounted) ss.setState(setState ?? () {});
},
);
}
}
@override
Widget build(BuildContext context) {
print(
"This build function will never be called. it has to be overriden here because State interface requires this");
return null;
}
}
typedef _StateBuildertype = Widget Function(State state);
class StateBuilder extends StatefulWidget {
@required
final _StateBuildertype builder;
final void Function(State state) initState, dispose, didChangeDependencies;
final void Function(StateBuilder oldWidget, State state) didUpdateWidget;
final String stateID;
final List<BloCSetting> blocs;
StateBuilder({
Key key,
this.stateID,
this.blocs,
this.builder,
this.initState,
this.dispose,
this.didChangeDependencies,
this.didUpdateWidget,
}) : assert(builder != null),
assert(stateID == null ||
blocs != null), // blocs must not be null if a stateID is given
super(key: key);
@override
_StateBuilderState createState() => _StateBuilderState();
}
class _StateBuilderState extends State<StateBuilder> {
@override
void initState() {
super.initState();
if (widget.stateID != null && widget.stateID != "") {
if (widget.blocs != null) {
widget.blocs.forEach(
(b) {
if (b == null) return;
b._stateMap[widget.stateID] = this;
},
);
}
}
if (widget.initState != null) widget.initState(this);
}
@override
void dispose() {
if (widget.stateID != null && widget.stateID != "") {
if (widget.blocs != null) {
widget.blocs.forEach(
(b) {
if (b == null) return;
if (b._stateMap[widget.stateID].hashCode == this.hashCode) {
b._stateMap.remove(widget.stateID);
}
},
);
}
}
if (widget.dispose != null) widget.dispose(this);
super.dispose();
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (widget.didChangeDependencies != null)
widget.didChangeDependencies(this);
}
@override
void didUpdateWidget(StateBuilder oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.didUpdateWidget != null) widget.didUpdateWidget(oldWidget, this);
}
@override
Widget build(BuildContext context) {
return widget.builder(this);
}
}

Conclusion:

The approach that I presented here and in my first article is so easy, so simple, so powerful, and so efficient that one no longer bears any concern on state management and spends the the majority of the time building beautiful designs with one sole ingredient “statelessWidgtes”.

In the flutter 2019 roadmap in section “Ease of Adoption”, your read :

  • Addressing best practices for managing state in a Flutter application.

with jargon of Dart, I say:

(flutterStateManagementIsAnOpenDebate).close();
assert(isStillOpened == flase);

Do you agree?