StatelessWidget vs. StatefulWidget
flutter-clutter
Posted on August 8, 2020
Probably one of the most frequently asked questions of Flutter beginners is about the difference of StatelessWidget
and StatefulWidget
. Coming from other frameworks, this distinction is a new decision to make and when creating new widgets you can't get around it. In this article, the difference of both widget types will be examined and the answer to the question why stateful widgets cannot simply always be used will be given.
What is the difference?
The meaning of stateless is merely that all of the widget's properties are immutable. This leads to the fact that the only way to change them is a new instance of that widget. Now the confusing part: a stateful widget is immutable as well. However, the state object it's coupled with, is mutable and allows the rebuild of the widget whenever the state changes.
In other words: A StatelessWidget
will never rebuild by itself, but it has the ability to do so from external. A StatefulWidget
can trigger the rebuild by itself (e. g. by calling setState()
).
As shown in the figure, the StatelessWidget
only offers one public API to trigger a build which is the constructor, whereas the StatefulWidget
has numerous triggers that cause a (re)build. didUpdateWidget()
is called whenever configuration of the widget changes. This can for example be the case when the screen rotates.
Common misconception
I often read the difference between StatelessWidget
and StatefulWidget
is its ability to change. While this is technically correct (although not very precise) because the widget itself is immutable, it can cause confusion. Statements like these led me to a misunderstanding when I was starting to learn Flutter.
I had thoughts like: "Well, if I have a button whose color needs to change depending on external influences, I need to use a StatefulWidget for the button because change is not possible otherwise". Actually, the color was part of the constructor and the parent widget was responsible for the color change, making the parent widget stateful and the button stateless.
Therefore, for me the relevant question is not "can the widget change over time?" but rather "who is in charge of a potential change?".
What do I need state for?
One could ask: why the big attention? What's the significance of state anyways?
If you think about it, user interaction is nothing more than reading from and writing to the state. Let it be the local state of a screen or the state of the whole app. As a user, you interpret what's going on visually (which is a representation of the app's state) and interact with it through UI components (which then apply changes to the state). By the time your app gains complexity, managing state across all screens or widgets becomes more of a challenge. That's why it's a good idea to think about the way you want to manage your state early on.
Why the distinction?
A legit question to ask would be: if a stateful widget is virtually the same as a stateless widget (apart from a state object being tracked or not), why don't we just use stateful widgets everywhere and call it widget? No confusion for the developer and no need to decide which one to use in which situation. Besides, if one can always turn a stateless widget into a stateful one by wrapping a stateful widget around it, what's the point? Why do we need distinction in the first place?
To approach the answer to that question, let's have a look at the setState()
method being located in the State class of the framework.
@protected
void setState(VoidCallback fn) {
assert(fn != null);
assert(() {
...
return true;
}());
final dynamic result = fn() as dynamic;
assert(() {
...
return true;
}());
_element.markNeedsBuild();
}
Actually, apart from some assertions and storing the result from the provided callback, the only thing setState()
does, is calling _element.markNeedsBuild()
.
Interesting, so can't we just call markNeedsBuild()
from a stateless widget then and achieve the same functionality as calling setState()
in a stateful widget?
class StatelessWidgetWithState extends StatelessWidget {
@override
Widget build(covariant TestState context) {
return MaterialButton(
onPressed: () {
context.text = 'Overridden';
context.markNeedsBuild();
},
child: Text(context.text),
);
}
@override
TestState createElement() => TestState(this);
}
By using covariant we were able to mimic the behavior in a stateless widget and indeed, this produces the same result like having set the context variable of a State inside the setState()
method of a stateful widget.
What that shows is: StatelessWidgets
and StatefulWidgets
provide the same functionality (internally) but StatelessWidget
hides most of it from the developer to provide a convenience class for widgets that are static.
Having a generic Widget
class that reacts dynamically on the calls and keeps the caller from deciding between stateless and stateful would require some reflection. In the context of the Flutter framework, Dart's ability of mirroring is disabled, though.
To conclude: the inability of having a single base class that works for both stateful and statless widgets is a design decision of the Flutter developers that increases the convenience and prevents the developer from doing things he should not do. It is also a consequence of how Dart works and what restrictions to the language were put into the framework.
How do I decide when to use a StatefulWidget?
If the visuals of the widget only change depending on the parent widget and all of its state is defined in the constructor, use a StatelessWidget
.
If there is an internal state (that could e. g. be a text color) that changes independent from the parent (e. g. a color changes when you tap it), use a StatefulWidget
.
If you are in doubt, start with a StatelessWidget
. Once you realize you're approaching its boundaries, you can easily switch to a StatefulWidget
by using your IDEs shortcuts.
Does a StatelessWidget become stateful when it contains a StatefulWidget?
If you look at it on a more abstract level, you can say so. But in the world of Flutter where stateful and stateless are predefined terms it does not. The parent has no knowledge or responsibility of the child's state. The stateful child manages its state on its own.
What if every widget was stateless?
If we were to create a completely static app that allows for no interaction, we could theoretically implement every widget stateless. That would, however, also mean that we could not navigate because the MaterialApp
, which we could not use because it is stateful, configures the top-level Navigator
. Basically, we would be faced with a single-screen app that could display text and a little layouting.
The official docs say this about the topic:
Entire applications can be built with only StatelessWidgets, which are essentially functions that describe how arguments map to other functions, bottoming out in primitives that compute layouts or paint graphics. (Such applications can’t easily have state, so are typically non-interactive.) For example, the Icon widget is essentially a function that maps its arguments (color, icon, size) into layout primitives. Additionally, heavy use is made of immutable data structures, including the entire Widget class hierarchy as well as numerous supporting classes such as Rect and TextStyle. On a smaller scale, Dart’s Iterable API, which makes heavy use of the functional style (map, reduce, where, etc), is frequently used to process lists of values in the framework.
What if every widget was stateful?
Like it was said before: internally there is no big difference between stateful and stateless widgets. And strictly speaking, every widget is stateful. However, using stateful widgets wherever we can would create unnecessary boilerplate code with tons of widgets that are never rebuilt and states that are never initiated. In terms of performance, that would not make a big difference. Regarding readability and LOC it's probably not the most recommendable approach.
Examples
Let's have a look at some examples to make the above explanations more clear. We are going to implement three little widgets:
- A widget using only its internal state
- A widget that get its initial and final state handed in from its parent
- A widget that uses both internal state and state being managed from its parent
Widget using only internal state
class StateOnlyWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _StateOnlyWidgetState();
}
}
class _StateOnlyWidgetState extends State<StateOnlyWidget> {
String text = 'Initial';
@override
Widget build(BuildContext context) {
return ListView(
children: <Widget>[
RaisedButton(
child: Text('Press'),
onPressed: _changeText,
),
Text(text)
],
);
}
void _changeText() {
setState(() {
text = 'Overridden';
});
}
}
The text property is managed by the widget's state in this example. No parent widget knows about this property as it's not publicly available throughout the widget. Once the user presses the button, the setState()
method is used to alter it.
Widget using only external state
class ExternalStateOnlyWidget extends StatelessWidget {
ExternalStateOnlyWidget({
@required this.textString,
@required this.changeText
});
final String textString;
final Function changeText;
@override
Widget build(BuildContext context) {
@override
Widget build(BuildContext context) {
return ListView(
children: <Widget>[
RaisedButton(
child: Text('Press'),
onPressed: changeText,
),
Text(textString)
],
);
}
}
}
In this case, both the string describing the text and the behavior that is triggered when tapping the button are controlled by the parent and injected into the constructor of our widget.
Mix-and-match
class InternalAndExternalStateWidget extends StatefulWidget {
InternalAndExternalStateWidget({
@required this.buttonText,
});
final String buttonText;
@override
State<StatefulWidget> createState() {
return _InternalAndExternalStateWidgetState();
}
}
class _InternalAndExternalStateWidgetState extends State<InternalAndExternalStateWidget> {
String text = 'Initial';
@override
Widget build(BuildContext context) {
return ListView(
children: <Widget>[
RaisedButton(
child: Text(widget.buttonText),
onPressed: _changeText,
),
Text(text)
],
);
}
void _changeText() {
setState(() {
text = 'Overridden';
});
}
}
This is a mix-and-match approach where the displayed text string and the behavior on button tap are managed internally by the widget. The label of the button is injected by the parent widget.
Final words
Although the difference between StatelessWidget
and StatefulWidget
might be confusing for beginners, after having invested a few minutes, it's not that difficult anymore. The decision what kind of widget to use mainly depends on the question if the widget has its own internal state to manage. If you are in doubt, you can always start with a StatelessWidget
first and convert later if it does not suit the use case.
After all, the distinction is merely a convenience functionality that removes the boilerplate when we use the stateless widget. Internally, both types are virtually the same.
Posted on August 8, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.