Flutter #OneYearChallenge; Scoped Model vs BloC Pattern vs States Rebuilder

MELLATI Fatah
Flutter Community
Published in
22 min readFeb 25, 2019

Flutter is declarative. This means that Flutter rebuilds its user interface (UI) from scratch to reflect the current state of your app each time setState() method is called. But what is a state? Roughly speaking, a state is the actual values of variables the app has in memory. State management is a set of techniques used to make the UI display the current state, and auto update if the state changes. In Flutter, state management techniques differ in: How, Where and when to call setState().

This article answers these How-Where-When questions for three techniques: scoped_model, BloC_pattern and states_rebuilder.

To put these three state management techniques under test, and to see how much the difference in the answer of the How-Where-When question impact the performance of these techniques, I choose to challenge them.

The challenge:

The UI of the challenging app consists of a horizontal ListView of counter itemCards. When the user taps on any itemCard from the ListView, a card appears in the detailed container under the ListView with the same color and count number.

UI of the challenge

The tricky part of the challenge is that if the user taps on the detailed container, the detailed counter is incremented and only the corresponding itemCard that has been topped in the ListView rebuilds to show the new counter value.

Detailed container is tapped 5 times

The itemCard are randomly colored to track the rebuild of each card because if any card rebuilds the random color changes.

This challenge is tricky because as I said Flutter is declarative and don’t have something like findViewByID. For Frameworks that have something like this, the challenge is easily done by imperatively finding the itemCard of particular ID and increment its counter, but for Flutter, it is not that easy.

Let’s give the microphone to the three state management techniques to speak about themselves.

Scoped_model:

Update note (Dec-2019): scoped_model is deprecated in favor of the provider package. But the provider package is more about dependency injection than state management. Nevertheless, the state management philosophy of the provider is similar to that of the scoped_model.

Hi my name is scoped_model, I’m a library that helps you handling data in your application to achieve neat separation of your logic form UI. I’m very happy that flutter team has recently dedicated to me an entire section in the official documentation. My uncle Tensor Programming told me that I was originated as a pattern found in Google’s Fuchsia OS repository. Also, he told me that Fuchsia used of Flutter in many of its native widgets and my pattern is used heavily in those widgets to manger their state.

How to use me?

To implement me! It is the easiest thing in the world. Just remember this: I have three classes and you have three steps.

I have three classes: Model, ScopedModel, ScopedModelDescendant.

You follow three steps:

1- Your logic class, YourModel, must extend my Model class. You have to do so to be able to call my notifyListeners() inside any of yourMethods.

This is a typical implementation of my Model: (after importing me!):

//scoped_model:
class YourModel extends Model {
Var yourVar; yourMethod(){ // some logic staff;
yourVar = yourNewValue;
notifyListeners(); // this is my method. Call it to update widgets
}
}
// Update:Dec-2019
// provider:
class YourModel extends ChangeNotifier{
Var yourVar;yourMethod(){// some logic staff;
yourVar = yourNewValue;
notifyListeners(); // this is my method. Call it to update widgets
}
}

2- In your UI part, wrap the top most widget that you want to make it as well as its children have access to YourModel class with ScopedModel:

//scoped_model
ScopedModel
(
model: YourModel(),
child : TopWidget()
) // wrong
// Update:Dec-2019
//provider
ChangeNotifierProvider(
create: ()=> YourModel(),
child : TopWidget()
) // Accepted because provider can infer the type

Oops! This is a common error beginners often do. It’s important to not forget to specify the generic type of your model:

//scoped_model
ScopedModel
<YourModel>(
model: YourModel(),
child : TopWidget()
)
// Update:Dec-2019
//provider
ChangeNotifierProvider<YourModel>(
create: ()=> YourModel(),
child : TopWidget()
)

2- Now in any of your widgets underneath the TopWidget and whenever you want to access YourModel and want the widget to be rebuilt after calling notifyListeners(), wrap this widget in ScopedModelDescendant

//scoped_model
ScopedModelDescendant
<YourModel>(
builder: (BuildContext context,widget child,YourModel yourModel) {
return ChildWidget(yourModel.yourVar);
}
)
// Update:Dec-2019
//provider
Consumer<YourModel>(
builder: (BuildContext context,YourModel yourModel,Widget child) {
return ChildWidget(yourModel.yourVar);
}
)

Again do not forget to specify the generic type.

That all, I have three classes you follow three steps, and your app is reactive.

Note: In case you want to access YourModel but don’t want the widget form where you get access to be rebuilt each time notifyListeners() is executed, for example when you want to call a method from YourModel, use:

//scoped_model
ScopedModel
.of<YourModel>(context).yourMethod();
// Update:Dec-2019
//provider
Provider.of<YourModel>(context, listen: false).yourMethod();

As many of you may know it before, you can get the same result as the above line of code with ScopedModelDescendant provided you set the optional parameter rebuildOnChange to false.

ScopedModelDescendant<YourModel>(
rebuildOnChange : false,
builder: (context,child,yourModel) {
yourModel.yourMethod();
}
)

How do I manage the state?

In the first step above, you extend YourModel with Model class. Behind the scenes, there is a Set of VoidCallback. Whenever you call notifyListeners(), the program iterates inside the Set and executes these VoidCallbacks. A voidCallback is nothing than (){setState();}. The question is: Where the voidCallbacks have been added to the Set. the answer in the second step.

In the second step, you use ScopedModel class to wrap the top most widget of your tree. The ScopedModel has two missions:

1- Under the hood, it uses the AnimatedWidget which is a StatefulWidget. In the initState of the AnimatedWidget, the VoidCallback is added to the Set.

addListener method adds a VoidCallback(_handleChage) to the Set.

Now when you call notifyListeners(), your TopWidget is rebuilt. Do all the child widgets rebuild with TopWidget? NO, It is the InheritedWidget!!!

2- The second mission of the ScopedModel is to make use of the InheritedWidget to provide YourModel to all descendant Widgets. An important feature of the InheritedWidget is that it stops the down propagation of the rebuild process. Only widgets that inherit from it using inheritFromWidgetOfExactType method will be rebuilt. The question is how to inherit form the InheritedWidget? the answer in the third step.

3- The role of ScopedModelDescendant is to make use of the ScopetModel.of(context) method to obtain YourModel instance, and if the rebuildOnChange is set to true, which is the default value, the wrapped widget will be attached to the InheritedWidget and will be notified to rebuild.

To the challenge

1- The logic part:

The MainModel class has four fields and three methods:

The variables are:

itemProvider: has all the methods related to fetching items from external providers;
items : a list of Item objects that will be displayed in the ListView;
detailedColor : is the color of the card shown in the detailed container; and
detailedIndex : is the index of the card shown in the detailed container.

The methods are:

getItems() : It is called from the initState to fetch on the items from the itemProvider and after getting them it calls notifyLisners();
shwDetailed(Color color, int index): It is called when the user taps on a card from the ListView. It is called with the tapped color and index. Then it mutate the detailedColor and detailedIndex and call notifyListeners() to rebuild the detailed container;
increment() : called when the user taps on the detained container to increment the counter of the item that have an index equal to detailedIndex.

This is all for the MainModel class.

2- The UI part:

lines 15–18 : instantiation of ScopedModel;
line 35 : calling the getItems() from the initState to fetch form items;

lines 45–68 : ScopedModelDescendant is defined to make the horizontal ListView reactive:
— — — line 46 : check if items List is null, then show a circular progress indicator, if not null show the ListView;
— — — lines 54–55 : for each card get a random color. each time the ListView is rebuilt the colors change randomly;
— — — lines 58–64 : ItemCard is a stateless widget to display a card;
— — — lines 61–62 : call of the showDetailed(randColor, index) when a card is tapped;

lines 71–84 : ScopedModelDescendant is defined to make the detailed container reactive:
— — — lines78–81 : display the card that has an index equals to detailedIndex and call increment() method when the detailed container is tapped.

And this is the end of the UI part. this is a GIF of the app:

As you can see, in the first 10 seconds when a card from the ListView is tapped the whole ListView as well as the detailed container are rebuilt (witnessed by the random colors change). This behavior is not expected. we don’t want the ListView to rebuild when any of its itemCard is tapped. This side effect is understood, because the rebuild process in the scoped model is controlled by the InheritedWidget which rebuilds any widget that inherit from it using inheritFromWidgetOfExactType, and this is the case for the ListView and the detailed container.

Perhaps one can think of switching the rebuildOnChange parameter in ScopedModelDescendant to false, but then, at the start of the app when data are fetched the ListView will not rebuild and the app stay stacked in the circular progress indicator.

When it comes to the challenge the scoped model fails to increment the card count without rebuilding the whole ListView.

In conclusion:

If your app is small, let’s say you have one task to go from A to B, with scoped_model you do it straightforward with some undesired side effect. The picture below schematized the one task app with straight filled arrow and dash arrow for the undesired rebuild.

schematic of one task simple app

The dashed arrow means that for small app, the rebuild of non-wanted widget is controlled by setting rebuildOnChange to false.

But when it comes to more complex apps, the undesired rebuild is inevitable.

schematic of 5 tasks complex app

The solid curved arrows are the inevitable side effects of rebuilding widgets we want them not to rebuild.

The scooped_model is declared losing the challenge with unexpected side effects.

Note: It is worth noting that the team behind scoped_model are working for a replacement of the scoped_model by other one named Provider. The backend idea of calling setState() inside AnimatedWidget and relaying on InheritedWideget to rebuild the widgets that inherited from it, is the same. From first judgement, the shortcoming mentioned for the original scoped_model is still relevant with the new Provider approach, because the latter always relays on InheritedWidget to manage widgets rebuild.

BloC Pattern

Hi, my name is Business Logic Component and my friends call me BloC. I’m a state management system for Flutter recommended by Google developers. I help you centralize your data and logic and give you the ability to access them from any widget in your UI. My parents are Cong Hui, Paolo Soares, and Wenfan Huang from Google. They conceived me to get the maximum code sharing between Flutter and AngularDart.

How to use me?

Do you know what is a stream? If yes, then you are in a good point to start using me. If no, I try to give you some basic information about streams.

Think of a stream as pipe filled with water that flows from the A side to the B side. Let’s say you are in side A and want to send some colorful tiny children’s balls to side B. You sink these balls one after the another inside the pipe and the water will transport them to side B one by one in a stream fashion. The balls exit the pipe from B side. Let’s say they fall and make a noise. Let’s say there is another person inside B waiting for the balls. Because this person doesn’t know when exactly a ball arrives, he decided to read a newspaper. It’s only when he listens the sound of a ball that he is aware of the arrival of a ball. At that time, he can catch the ball and make use of it.

In real BloC, the pipe is StreamController, the flow of water is StreamController.stram, the action of pushing balls from side A is StreamController.sink, colorful children’s balls are data of any type, and the person in the B side listening to ball falling is StreamController.stram.listen.

Enough of analogy. In Dart document you read about StreamController:

This controller (StreamController) allows sending data, error and done events on its stream. This class can be used to create a simple stream that others can listen on, and to push events to that stream.

To implement me, for each of your variables you need to define five things:

1- Your variable name; (not necessary if you use rxDart)
2- StreamController;
3- stream
4- sink;
5- Close StreamController.

This is how a typical bloc class should look like:

class YourBloc {  var yourVar;  final yourVarController = StreamController<yourType>();  Stream<yourType> get yourVarStream => counterController.stream;  StreamSink<yourType> get yourVarSink => counterController.sink;  yourMethod() {    // some logic staff;
yourVar = yourNewValue;
yourVarSink.add(yourVar);
}
dispose() {
yourVarController.close();
}
}

This is only basic. The StreamController, you used above, is for one single subscriber (listener). If you want to listen to the same stream many times you have to use StreamController.broadcast.

BloC pattern is often used with the third party library RxDart because it has many features not available in the standard dart StreamController.

To use RxDart, be careful of the naming convention. StreamController in dart becomes Subject in RxDart, Stream in dart are Observable in Rx and listen in dart is subscribe in RxDart.

To get the same functionality as StreamController.broadcast<T>, use PublishSubject<T> class in RxDart. But if you want any new listener to get the latest value add to the stream use BehaviorSubject<T> class.

I’m sure beginners find me a little complicated. But whenever they are used to me, they will love me.

I’m hearing some of you complaining form the amount of boilerplate you have to use to make a simple app reactive. Even my co-creator Paolo Soares is not happy with that:

https://twitter.com/paolo_soares/status/1089966367833509888

But, again, I’m certain that when it comes to more complex apps, I am the best choice.

How do I manage the state?

I manage the state with the help of StreamBuilder which extent a StatefulWidget named _StreamBuilderBase.

Inside the initState of StreamBuilderBaseState, I start listening to the stream you have provided in the “stream” parameter, and execute the setState() whenever the stream received data to rebuild my child widgets.

Part of _StreamBuilderBaseState class where setState() is called

To the challenge

1- The logic part:

As in the scoped model case, in the logic we need four variables and three methods:

The idea is that for each variable that we want use it in the UI, we need to define up to four fields.

The variables are:

itemProvider: has all the methods related to fetching items from external providers; (this variable will not be used in the UI);
items : is a list of Items. We want to use it in the UI, also we want to mutate it form the BloC. For this reason, we need to define four fields: (_items, itemsConroller, itemsStream, itemsSink):

  • List<Item> _items;
  • itemsConroller = StreamController<List<Item>>.();
  • Stream get itemsStream => itemsConroller.stream;
  • StreamSink<List<Item>> get itemsSink => itemsConroller.sink;

detailedColor : is the color of the card shown in the detailed container. So we want to use it in the UI, but we don’t want to mutate it in the BloC. for this reason, we need three fields: (detailedColorConroller, detailedColorStream, detailedColorSink):

  • detailedColorConroller = StreamController<Color>();
  • Stream get detailedColorStream => detailedColorConroller.stream;
  • StreamSink<Color> get detailedColorSink => detailedColorConroller.sink;

detailedIndex : is the index of the card shown in the detailed container. So we want to use it in the UI, but we don’t want to mutate it in the BloC. for this reason, we need three fields: (detailedItemConroller, detailedItemStream, detailedItemSink):

  • final detailedItemConroller = StreamController<Item>();
  • Stream get detailedItemStream => detailedItemConroller.stream;
  • StreamSink<Item> get detailedItemSink => detailedItemConroller.sink;

Do not forget to close all the StreamControllers.

The methods are:

getItems() : It is called from the initState to fetch on the items from the itemProvider and after getting them it calls itemsSink.add(_items) to rebuild the StreamBuilder that is listening to the itemsStream;
shwDetailed (Color color, int index, Item item): It is called when the user tapped on a card from the ListView. It is called with the tapped color, index and item. It call detailedColorSink.add(color) and detailedItemSink.add(item) to rebuild the StreamBuilders that are listening to them and save the provided index in a private variable _detailedIndex to be used to in the increment method;
increment() : called when the user taps on the detained container to increment the counter of the item that have an index equal to _detailedIndex. that it calls itemsSink.add(_items) to rebuild the ListView and detailedItemSink.add(_items[_detailedIndex]) to rebuild the detailed container;

To get to this code I passed through many try and error process and many error fixing.

2- The UI part:

lines 22–46 : StreamBuilder of the item list.
— — — line 23 : listening to itemsStream,
— — — line 24 : cheking if snapshot don’t has data to show the circular progress indicator, or if it has data to show the ListView.
lines 53–67 : StreamBuilder of the detailed item.
— — — line 53–67 : nested StreamBuilder for the detailed color.

this is a GIF of the app with BloC pattern:

In contrast to scoped_model, when a card from the ListView is tapped the ListView does not rebuild. This is a point in favor ofthe BloC pattern. But the BloC does not pass the challenge because it fails to increment the card count without rebuilding the whole ListView. I can say that we can no do that, but if there is a way to pass the challenge it would be very complicated.

In conclusion:

For small app, the BloC pattern tends to complicate the uncomplicated. For one task app, let’s say to move from A to B, you have to define a StreamController, a stream, a sink and dispose the StreamController.

When it comes to more complex app, the situation is worse especially if you constrain yourself to use only streams.

schematic representation of multi task complex app in BloC pattern

States_rebuilder

Update note (Dec-2019): states_ rebuilder know has what is called explicit reactivity and implicit reactivity. Explicit reactivity means that you have to make your logic class to extend StatesRebuilder and to explicitly notify listeners using rebuildStates method. In Implicit reactivity, your model is a pure dart class without any extension of whatsoever. It is the duty of the library to add reactivity for you, thus, leaving your model clear, simple, testable, maintainable, and framework independent.

Hi there, my name is states_rebuilder, almost all of you don’t know me. It is very difficult for newcomers to have a place in the flutter state management field with the presence of scoped_model, BloC, Redux, and many other third libraries. But anyway I’ll try.

My creator is MELLATI, the author of this article. He wrote three articles about state management in flutter to introduce me. (article 1, article 2, article 3).

How to use me?

I am the simplest state management technique to implement. Do not believe me? Just follow me.

Before going through steps of implementation, I have to point out something important. Sometimes getting new ideas is easier than baptizing it. That exactly what happened to me. I have three choices to name my logic classes:

1- BloC, but this often confused with the BloC pattern which is based on streams;
2- Model as in scoped_model, but this confuses me. Model has another meaning in MVC, MVP, and MVVM popular design patterns;
3- ViewModel, I think is the most suitable term because what The ViewModel does in MVVM is to store states, mutate states, and expose change events when the states change to update the view.

But BloC is a fancy word and I like it. So I’ll use it provided you don’t think of streams anymore.

OK, to implement me remember: I have two classes and you have two steps:

I have two classes: StatesRebuilder and StateBuilder, you follow two steps:

1- Your logic class, YourBloc, must extend my StatesRebuilder class to be able to use my rebuildStates method:

This is a typical implementation of my StatesRebuilder:

Import "states_rebuilder/states_rebuilder.dart";1- Explict reactivity:class YourBloc { var yourVar; /// You have two alternatives:
/// First alternative : Notify all listeners
yourMethod1() { // some logic staff;
yourVar = yourNewValue;
rebuildStates();
}
// Second alternative (tag alternative): filter notification by tagyourMethod2() { // some logic staff;
yourVar = yourNewValue;
rebuildStates([“yourStateTag1”, “yourStateTag2”]);
}
}
// Update:Dec-2019
2- Implicit reactivity:
A pure vanilla dart class without extantion or notification.
class YourBloc {var yourVar;yourMethod1() { // some logic staff;
yourVar = yourNewValue;
}
}

rebuildStates method is equivalent to notifyListeners in the scoped_model.

As you can see, you have two alternatives to call the rebuildStates method:

  • The first alternative (notify all listeners): When rebuildStates is called without arguments it will notify all listener widgets.
  • The second alternative (or the tag alternative): in the UI you define tags to the widgets that you want to be rebuilt from the BloCs. In yourMethod you call the rebuildStates with any number of tags you want. (rebuildStates([“yourStateTag1”, “yourStatetTag2”]); )

The question is: How to add tags to widgets? The answer is in the next step.

2- In your UI and after providing your blocs, wrap any widget you want to be able to rebuild from the BloC with Statebuilder wiget, give it a tag and define the list of models you want to use the tag from. This is the first alternative:

StateBuilder(
models : [yourBloc]
tag: “yourStateTag1”, // tag is optional
builder: (_,__) => YourChildWidget(yourBloc.yourVar),
)

With this, you can call yourMethod1 from any place in the UI to rebuild the YourChildWidget.

For example, in another place in your UI even from another file, you can have:

RaisedButton(
..
onPressed : yourBloc.yourMethod1,
)

tag parameter is optional and can be omitted. In this case, the widget will be notified using rebuildStates() without arguments.

states_rebuilder creates a default tag to each StateBuilder widget which is the BuildContext of the widget.

StateBuilder(
models : [yourBloc]
builder: (BuildContext context, _) => YourChildWidget(
//context is the default tag
yourBloc.yourVar,
onPressed: ()=>yourBloc.yourMethod2(context),
),
)

The default tag is very useful if you are to render a list of widgets using ListView or any similar widget. Imagine you have 100 ListTiles, it’s not practical to give them all tags. This gives you the ability to rebuild any of these 100 ListTiles from the blocs without rebuilding the whole ListView (see the challenge section).

StateBuilder offers more options to use. This is the class constructor:

StateBuilder<T>(
onSetState: (BuildContext context, ReactiveModel<T> model){
/*
Side effects to be executed after sending notification and before rebuilding the observers. Side effects are navigating, opening the drawer, showing snackBar,...
*/
},
onRebuildState: (BuildContext context, ReactiveModel<T> model){
// The same as in onSetState but called after the end rebuild process.
},
initState: (BuildContext context, ReactiveModel<T> model){
// Function to execute in initState of the state.
},
dispose: (BuildContext context, ReactiveModel<T> model){
// Function to execute in dispose of the state.
},
didChangeDependencies: (BuildContext context, ReactiveModel<T> model){
// Function to be executed when a dependency of state changes.
},
didUpdateWidget: (BuildContext context, ReactiveModel<T> model, StateBuilder oldWidget){
// Called whenever the widget configuration changes.
},
afterInitialBuild: (BuildContext context, ReactiveModel<T> model){
// Called after the widget is first inserted in the widget tree.
},
afterRebuild: (BuildContext context, ReactiveModel<T> model){
/*
Called after each rebuild of the widget. The difference between onRebuildState and afterRebuild is that the latter is called each time the widget rebuilds, regardless of the origin of the rebuild. Whereas onRebuildState is called only after rebuilds after notifications from the models to which the widget is subscribed.
*/
},
// If true all model will be disposed when the widget is removed from the widget tree
disposeModels: true,
// A list of observable objects to which this widget will subscribe.
models: [model1, model2]
// Tag to be used to filer notification from observable classes.
// It can be any type of data, but when it is a List,
// this widget will be saved with many tags that are the items in the list.
tag: dynamicbuilder: (BuildContext context, ReactiveModel<T> model){
/// [BuildContext] can be used as the default tag of this widget.
/// The model is the first instance (model1) in the list of the [models] parameter.
/// If the parameter [models] is not provided then the model will be a new reactive instance.
},
builderWithChild: (BuildContext context, ReactiveModel<T> model, Widget child){
///Same as [builder], but can take a child widget containing the part of the widget tree that we do not want to rebuild.
/// If both [builder] and [builderWithChild] are defined, it will throw.
},
//The child widget that is used in [builderWithChild].
child: MyWidget(),
)

initState, dispose, didChangeDependencies, didUpdateWidget have the same meaning as in StatefulWidget. For example, you can execute some code in the initState to fetch some data from an API. You can pass the tag (context by default) to the bloC to rebuild it when you get the data.

StateBuilder(
initState : (context,_) => yourBloc.yourInitMethod(context),
builder: (_) => YourChildWidget(
yourBloc.yourVar,
),
)

in the BloC you have something like this:

class YourBloc extends StatesRebuilder{var yourVar;yourInitMethod(context )async {   await fetchItemsFromAPI();
yourVar = yourNewValue;
rebuildStates([context]);
}

Endnote: If you followed my last articles you may have noticed that I changed the naming convention a little bit:

  • BloCSetting becomes StatesRebuilder
  • rebuildWidgets becomes rebuildStates.

How do I manage the state?

As you can see, both scoped_model and BloC techniques end in calling setState() method somewhere in the code. scoped_model calls setState() inside an animatedWidget, and BloC inside StreamBuilder. I’m not an exception, I use the observer pattern and call setState(), in the update() method of the StateBuilder widget:

and when you call rebuildStates method, you and calling the update() method of all registered StateBuilder widgets.

To the challenge:

the MainBloc is as simple as in scoped_model MainModel. actually it looks similar to the scoped_model logic with one and important difference:

I add a variable of type State named tappedCardTag. this variable is mutated in showDetailed method to hold the tag of the tapped card. When increment method is called the item of index equals detailedIndex is incremented and ONLY DETAILED CONTAINER (detailedWidgetState) AND THE TAPPED CARD (tappedCardTag) ARE REBUILT.

NB: I provided the BloC globally for simplicity.

the UI part of the challenge:

lines 09–36 : StateBuilder is defined to make the horizontal ListView reactive.
— — — line 12: fetchItems(context) is called from the initState parameter, “context” here holds the tag of the widget to be used inside the mainBloc.
— — — line 13: check if items List is null, then show a circular progress indicator, if not null show the ListView;

lines 25–37 : StateBuilder is defined to make each individual itemCard reactive. It is defined using default tag because we will rebuild the widget from a function (showDetailed) called from the inside:
— — — line 31: the builder parameter is defined with “context” as function argument; the “context” is passed to showDetailed(randColor, index, context) to update the tappedCardTag to hold this tag of the tapped card in the bloc.

lines 42–54 : StateBuilder is defined to make each individual detailed container reactive. It is defined using user-defined tag because we will rebuild the widget from a function (showDetailed) called from the outside.

That is all, the simplest of the tree approach. Let’s see if it passes the challenge:

Voila, the states_rebuilder wins the challenge.

little bug:

in the GIF below, if you tapped on the detailed container to increment the first itemCard, then if you scroll the ListView to the right until the first itemCard no longer visible. now if you scroll back to the first itemCard and top on the detailed container the itemCard does not update.

This behavior is expected because ListView.builder deposes itemCards that are not displayed and creates them if they are displayed again. In our case when we scroll back to display the first itemCard, a new BuildContext is created which is not the same as the BuildContext store in the tappedCardTag.

To fix this bug we have to update the value of tappedCardTag to hold the newly create BuildContext of the itemCard. Here how to do it:

line 21–32 in UI code above are updated by adding the highlighted line:

in the initState parameter, we call a new method from the mainBloc updateTappedState with state and index as parameter. in the bloc we add the updateTappedState method:

Each time an ItemCard is created, the updateTappedTag is called. we check if the index of the itemCard is equal to the detailedIndex and assign the passed state to the tappedCardTag. that all to do to fix the bug

Animation with states_rebuilder

states_rebuilder is so simple and powerful that we can separate animation setting from the UI. With states_rebuilder and only with it, animation setting is done inside the BloC and the UI is totally free of any animation mess.

look at this UI part:

the other part of the code is the same as the basic example. As you can see there nothing related to animation in the UI except FractionalTranslation which is a simple widget from the basic flutter framework.

This is how increment() methods look like to animate the cards:

here is the resulted animation from the above code:

Actually when it comes to animation I have many challenges. With states_rebuilder I can control the animation of hundreds of tiny widgets. look at this LED sign:

this is the UI part:

a simple GridView.count and a Container with an animated color value form the mainBloc.

super easy

In conclusion:

For small app, the states_rebuilder is the simplest with no side effects:

Schematic representation of one task app

When it comes to more complex app, the state management is still simple with full control on which widget to rebuild. The complexity lays in how much your logic is complex and on the number of pages you have but not on the state management.

Schematic representation of multi task app

states_rebuilder is the easiest, the most clean and the most powerful. With states_rebuilder you can achieve better-designed app and cleaned logic with little code.

Now to the prize distribution ceremony :

https://www.dreamstime.com/royalty-free-stock-photography-award-distribution-sport-competition-prize-ceremony-image32677707

No matter which of the state management technique you use and support, let’s show our great fluttersmanship (sportsmanship) and applaud the winner with +50 claps 👏 👏.

https://en.wikipedia.org/wiki/Sportsmanship#/media/File:USMC-110507-M-GR773-089.jpg

The link to the full code of this article will be available in comments sooner.

--

--