Let me share my insights after working with 2 years with Flutter, Bloc and StatefulWidget
s.
Whenever you find yourself in a situation that you want to make two StatefulWidget
communicate, then think twice if Bloc is the answer! I have gone too far with bloc-everything approach as was suggested in their docs
and expected that bloc
would help me achieve better modularity in code. I used BlocListener
s to utilize listener:
argument and call setState()
in it... Then I tried with BlocBuilder
s. I have always reached the dead end and got many bugs while integrating StatefulWidget
closely with Bloc
. Why, you ask? Just to achieve this cleany, shiny modularity to have widgets in separate files and make them reusable and all that clean-code jazz.
However, that's not the case for StatefulWidget
when it comes to practice.
Let me give you my mental model while approaching designing the widget. As for the example I will work on the following UI component - the vertical Column
that contains two ListItem
s that every one composes of two Switch
es, where the top switch toggles the bottom one. When a parent preference switch is toggled off, then it also disables the child one.
Sample UI:

Video with such requirement to be fulfilled:

How to code such UI?
The first idea is to create:
ParentPreferenceWidget.dart
ChildPreferenceWidget.dart
TitleSectionWidget.dart
Column would look like:
Column(
children: [
ParentPreferenceWidget(...),
ChildPreferenceWidget(...),
TitleSectionWidget(...),
ParentPreferenceWidget(...),
ChildPreferenceWidget(...),
TitleSectionWidget(...),
]
)
Ok, so the UI was coded. And how to make those Swtich
es communicate? (ParentPreferenceWidget
with ChildPreferenceWidget
one).
Well, the problem arises, as ParentPreferenceWidget
and ChildPreferenceWidget
will have a have Switch
widget.
A Switch
widget needs a value:
parameter and is capable of running animation.
In every tutorial it is required to call setState
for such Widget which aggregates Switch
. So such widgets need to extend StatefulWidget
.
A note for StatefulWidget
s - their associated State
objects have longer lifecylce than StatelessWidgets
. And it made because of
performance reasons. Resource here.
So State
objects are not destroyed during a widget tree rebuild phase, while StatelessWidgets
are destroyed and recreated. This enables running the animation smoothly for the first case.
So how to use bloc
then?
This could come to your mind (pseudo-code):
Column(
children: [
BlocProvider<...>(
create: ...
child: Column(
children: [
ParentPreferenceWidget(...), // will fire bloc events
BlocBuilder<...>(
builder: (context, state) => ChildPreferenceWidget(state, ...) // this will rebuild
)
]
)
),
TitleSectionWidget(...),
BlocProvider<...>(
create: ...
child: Column(
children: [
ParentPreferenceWidget(...), // will fire bloc events
BlocBuilder<...>(
builder: (context, state) => ChildPreferenceWidget(state, ...) // this will rebuild
)
]
)
),
TitleSectionWidget(...),
]
)
Beware! Whenever event is fired, a new instance of PreferenceWidget
is created. We've just said before that this is a StatefulWidget
, hence no performance gain at all.
And also there will be bloc
events multiplication (either you create separate bloc or reuse one). Either the new states are added and code complexity will grow.
And what if you want to move bloc higher? Like it would become a master one, so two ParentPreferenceWidget
can communicate.
Then what you need to do, is to add more data to bloc events like bool wasFiredFromBlocA
. This becomes a bloc hell.
Unfortunately, the only reasonable solution I came up with was to return to the basics and write the whole ParentPreferenceWidget
and ChildPreferenceWidget
functionality inside a single Stateful
widget.
This is motivated by the problems mentioned about while using single or multiple Bloc
s and making them communicate the state to child widgets.
So for my newly created Parent-Child widget, a State
object contains two fields, like this:
bool _switchParentState;
bool _switchChildState;
And voila - this way state objects live as long as expected and handle the state correctly.
As for the bloc
- you can manage such internal state with the help of BlocListener
if you really want to listen to some parent bloc from the 'outside'.
This could look like this:
return BlocListener(
listener: (context, state) {
setState(() {
// use state to set state
});
},
child: ... // build the widget tree normally.
)
A note - just remember to use ValueKey
or some other Key
for any StatefulWidget
that is duplicated inside the view hierarchy. As they need to be differentiated by the Flutter framework in case of State
objects.
And that's how I perceive this after struggling a lot. The bloc
is a great solution for simple cases. But when it comes to framework principles and some complex state mutations, it is better to stick to the basics.