Do I need a StatefulWidget if my app is using bloc?
Solution 1
When using StreamBuilder
you are in fact using a StatefulWidget
which listen to that Stream
. The only difference is that you don't write setState
yourself.
Another common use-case is for animations. If you want transitions such as fade/translate/whatever; you'll have to use AnimationController
. Which you will store inside a custom StatefulWidget
Solution 2
does it behoove me to use a StatefulWidget, where I'm using the bloc method anyway? I guess more specifically, why would I want to complicate my app using streams and states, when I could just use streams, wrap what I need to in a provider, wrap some widgets in a streamBuilder, and call it a day?
The answer to your questions depends on what your goals are.
A StatefulWidget does not scale to larger applications. The BLOC pattern does.
Why is StatefulWidget not good for larger applications?
Communicating information from one screen to a sibling screen tends to be challenging, meaning you have to write a lot of code to transfer data from one screen to another. It is possible, but it tends to be a pain and that's what the BLOC pattern solves.
It makes it easy to share information between multiple widgets inside our application.
BLOC stands for Business LOgic Component and its idea is to house all the data or state inside the application inside one area. It's outside the rest of the application and makes it easy to access.
That's different from a StatefulWidget, because with a BLOC all the data can live within one class outside the component hierarchy. So the state is being centralized to some outside object.
So with the BLOC pattern you do need a solid understanding of streams and you mentioned the StreamBuilder Widget which is were flutter and streams really come together.
The StreamBuilder takes a stream and a builder function and anytime that StreamBuilder sees a new piece of data it will call the builder function and re render itself on our mobile device, so it would look something like this:
Widget emailField() {
return StreamBuilder(
stream: bloc.email,
builder: (context, snapshot) {
return TextField(
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
hintText: '[email protected]',
labelText: 'Email Address',
),
);
});
}
The above approach being a single global instance which works fine for small application.
The provider you mentioned is a class that extends the inherited widget base class.
import 'package:flutter/material.dart';
import 'bloc.dart';
class Provider extends InheritedWidget {
final bloc = Bloc();
bool updateShouldNotify(_) => true;
static Bloc of(BuildContext, context) {
return (context.inheritFromWidgetOfExactType(Provider) as Provider).bloc;
}
}
Solution 3
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.
Solution 4
You can use a StatelessWidget
or a StatefulWidget
and everything will work fine but, in those instances where I have needed to initialise state in my bloc, I have used a StatefulWidget
and put my initialisation logic inside the initState()
override.
(please correct me kindly if this isn't best practice, I'm new to bloc, flutter and streams!)
Academiphile
I am an Electrical Engineering student, with a love for learning. I often browse the stack exchange network in my free time. I love Linux, and I'm half-decent at back-end programming. In my free time, I write.
Updated on December 06, 2022Comments
-
Academiphile over 1 year
I'm missing something.
I recently watched the talk here, where the flutter devs are going through using the bloc development method with reactivex in Dart. If I'm using these streams and streamBuilders to manage data flowing through my app, and rebuild appropriately, does it behoove me to use a StatefulWidget, where I'm using the bloc method anyway? I guess more specifically, why would I want to complicate my app using streams and states, when I could just use streams, wrap what I need to in a provider, wrap some widgets in a streamBuilder, and call it a day?
-
boformer almost 6 yearsAlso for text input fields (
TextInputController
) -
TBG over 2 yearsNow that you've got some experience (I assume), would you still do that ?
-
Adam about 2 years
A StatefulWidget does not scale to larger applications. The BLOC pattern does
What utter opinionated nonsense. Not that I disagree with BLoC pattern, but the statement that "StatefulWidget" won't scale is silly and wrong. -
Mário Meyrelles about 2 yearsThanks for such a detailed explanation. I will consider this idea :)