Defining widgets inside initState or class constructors rather than build?
TL;DR: Don't.
First, let's see what Googler and Flutter engineer Matt Sullivan has to say about this topic:
With Flutter creating and destroying objects with great frequency, should developers take steps to limit this behavior? It’s not uncommon to see new Flutter developers create references to widgets they know will not change over time, and place them in state so that they won’t be destroyed and rebuilt.
Don’t do this.
In order to understand why this is a bad idea, you first of all need to understand that Widget
s do very little.
Because they usually just compose other Widget
s or instantiate RenderObject
s, they don't do the actual rendering; all the heavy lifting is done by RenderObject
s.
You can think of the Widget
s as super-lightweight "blueprints" for RenderObject
s.
Also note that RenderObject
s get heavily cached by the Flutter framework itself, so if you create some similar Widget
in consecutive builds, the underlying RenderObject
is automatically reused.
By caching your UI, you are effectively re-creating a functionality which Flutter's rendering pipeline provides out of the box.
Solely because of that, many people would argue the added complexity to your code isn't worth it.
"Okay then," you might think, "The performance benefit may be very very small, but it does exist, right?"
Actually, no.
That's because the underlying Dart runtime uses two types of garbage collectors: the Young Space Scavenger garbage collector for short-lived objects and the mark-sweep garbage collector for long-lived objects.
For a more detailed explanation about how they work, check out this article fittingly named "Don't Fear the Garbage Collector".
Basically, it says that the Young Space Scavenger is much faster than the mark-sweep collector and if an app doesn't adhere to the "weak generational hypothesis" which states that most objects die young, then the mark-sweeping will occur more often.
Put short, performance might actually get worse.
To sum it up, re-implementing a built-in Flutter feature while making your code less readable and your app slower is probably not the right way to go.
This is a legitimate optimization. In fact you can even do the same with state dependent widgets (combined with didUpdateWidget
).
The win is negligible though.
Widgets are extremely light, and Dart is optimized for a lot of micro instantiations.
And there's one problem wih this approach: you loose hot-reload.
Reusing old widget instances is still pretty useful. As if the widget instance don't change, Flutter abort the subtree build.
This is used very often in animations to not rebuild the entire widget tree every frames. A typical example would be AnimatedBuilder
(but all XXTransition
follow the same logic)
Animation animation;
AnimatedBuilder(
animation: animation,
child: Text('Foo'),
builder: (context, child) {
return Align(
alignment: Alignment(.5, animation.value),
child: child,
);
},
);
Here, this voluntarily reuse the child
instance so that Text
build method doesn't get called again.
So, should I do it or not?
Well, yes and no. It is always cool to optimize your application. But instead of doing it using variables there's a better way : const constructors.
To reuse your example, you could extract your "always the same" widget tree into a custom widget with a const constructor:
class _Foo extends StatelessWidget {
const _Foo({Key key}): super(key: key);
@override
Widget build(BuildContext context) {
return Text(
'Oeschinen Lake Campground',
style: TextStyle(
fontWeight: FontWeight.bold,
),
);
}
}
and then use it this way inside your build
method:
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.only(bottom: 8.0),
child: const _Foo(),
);
}
This way you get the benefits of caching the widget instance. BUT you don't loose hot reload.
Perfect right?