Ok, I just tried again to wrap my head around this topic on what improves UI rebuilding performance.
In Investigate `const`ness · Issue #149932 · flutter/flutter · GitHub
Michael Goderbauer makes the case that the canonical call for making all widgets const might not have a big effect on performance in real word apps.
Indeed if we think about it const will only save use the instantiation of a Widget, the build method still runs.
The other topic that is discussed again and again is if StatelessWidgets are really faster than a helper method returning a part of the widget tree.
Especially if the StatelessWidget is not const which if I understand this correctly will prevent Flutter from easily decide if the widget has changed so it will normally always rebuild if it’s parent rebuilds.
Besides many source just claiming that widgets are faster I also found several discussions where people claimed to have made extensive benchmarking and having found no difference.
So does using helper methods really decrease build performance compared to non const StatelessWidgets and if so how big is the effect.
Important: I completely understand that using widgets has other positive aspects but I want to finally settle if performance really is the reason why many call Helper functions a antipattern.
@CraigLabenz you made a video on this in the past but unfortunately I didn’t fully feel convinced on the performance aspect that you describe. @kevmoo maybe you can give us a clear answer here?
Please instead of just reiterating that helper functions are bad, let’s try to really understand the pros and cons of them.
I found some really good info on this subject in the Element.rebuild docs. To paraphrase:
If a widget instance is identical to the previous version (and the element is not already marked dirty for other reasons), then the element skips updating itself.
If a widget is constructed using the const keyword, the same instance is returned each time it is constructed with the same arguments.
class _MyState extends State<MyWidget> {
// ...
Widget helperMethod(BuildContext context) {
final data = Provider.of<MyData>(context);
return Text('$data');
}
@override
Widget build(BuildContext context) {
return Column(
children: [
helperMethod(context),
// etc.
],
);
}
}
Turning that helper method into a StatelessWidget with a const constructor:
Makes it so the provider only rebuilds the Text (as opposed to rebuilding the entire stateful widget)
Allows the stateful widget to rebuild without triggering a rebuild of that helper method
One thing I heard about the benchmarking from flutter/flutter #149932 is that the codebase in question was using a localization API that was getting in the way of using constant values.
And back before I was familiar with providers/inherited widgets, I would often rely on passing the relevant data inside the build method, and unfortunately a const lint does not help there.
Depending on how a benchmark is contrived, it’s possible for the const keyword to give a 6,000% performance boost!
So I guess the TL;DR is: const constructors have the potential to be super helpful, but optimal performance comes from an interaction of multiple different concepts.
At least I would be surprised if Michael and some others of the Flutter core team would make such a misjudgement when benchmarking this.
Also if I’m not mistaken, const really only saves the allocation and initialization of the widget object. The build methods of that widget still have to run.
What about non const StatelessWidget vs helper function?
According to my knowledge, refactoring a helper function into a non-const stateless widget does not improve performance, unless you pull some shenanigans and bring it outside the build method:
class _MyState extends State<MyWidget>
with TickerProviderStateMixin {
late final controller = AnimationController(vsync: this);
// can be passed to the build method,
// rebuilding the StatefulWidget won't rebuild this guy!
late final veryCoolWidget = _VeryCoolWidget(controller);
}
Though in many cases, the same can be achieved by using the result of the helper method as the late final value. And it’s good to exercise caution when bringing things outside the build method, since sometimes updates won’t happen even when you expect them to.
(Edit: IMO developers who are new to Flutter are less likely to take full advantage of various state management tricks involving const, and more importantly might find the developer experience quite frustrating compared with the benefit that constant objects provide. So I agree that having it turned off by default was a good call.)
I read in one discussion that you can improve performance of StatelessWidgets by overriding the == operator to control when it will be seen as changed to avoid rebuilds.
That would be something that would not be possible with a helper function.
It is strongly advised to avoid overriding operator == on Widget objects.
While doing so seems like it could improve performance, in practice, for non-leaf widgets, it results in O(N²) behavior. This is because by necessity the comparison would have to include comparing child widgets, and if those child widgets also implement operator ==, it ultimately results in a complete walk of the widget tree… which is then repeated at each level of the tree. In practice, just rebuilding is cheaper. (Additionally, if any subclass of Widget used in an application implements operator ==, then the compiler cannot inline the comparison anywhere, because it has to treat the call as virtual just in case the instance happens to be one that has an overridden operator.)
Amdahl’s rule. Even if const/separate widget makes potentially a huge difference in the performance of instantiating/reusing a Widget, you have to look at how much of your app’s runtime is spent doing that.
I’m simplifying here, but: if you have a hundred widgets that could be const and aren’t, that’s 60x100 more expensive rebuilds per frame at most (if you’re rebuilding the whole app every frame). This is peanuts in most situations. You’re probably losing efficiency elsewhere. TL;DR always measure.
@filip I totally agree on the measurements. I just wanted to get to the bottom of this because if you google for this you mostly just find kind of dogmatic statements without explaining what is the underlying mechanisms.
Years ago I tried to concoct the most demented, worst-case scenario situation to demonstrate a difference in rebuild times using const vs not. (It was something like 100s of constantly animating Position widgets within a Stack, iirc.)
To my surprise, I saw only the tiniest, marginal difference. I looked at it with Greg Spencer and Michael Goderbauer to make sure I wasn’t undermining my own test, but we all concluded that the test was valid. Of course, the space of possible apps is big and this did not rule out any need to ever use const, but it did rule out significant importance in the sort of edge case I’d thought would be most beneficial.
More broadly speaking - issues of widget rebuilds and const get so app-specific that it’s very hard to publish specific guidelines that most users can rely upon. This is why the guidance has remained somewhat light - we try to explain the raw principles, highlight what rules of thumb ARE safe (a const widget will never slow you down, for example), and hope Flutter developers are equipped with the deep understanding required to reason about their specific scenarios.
Has anyone tried to play with the idea of adding a “signature” field to the widget? The signature is a 64-bit hash of the values widget depends on. Say widget depends only on the current values of the variables x,y. Computing their hash, we provide enough information to the system to detect modification.
(the hash is not supposed to include the fields that remain constant during runtime)
The way I understand it is that a helper function is just a simple abstraction—it’s no different from having everything within your build method. However, extracting parts of your UI into a new widget class creates a new identity for that subtree, allowing the diffing algorithm to perform equality checks on a smaller, more isolated portion of the widget tree. In theory, this makes it quicker to identify which parts of the UI have changed and which parts can be left alone, ultimately saving CPU cycles and thus improving performances.
On a side note, regarding const: it would be nice if the IDE could automatically remove the const that’s causing a warning/error when a widget property changes to something that can’t be made const, rather than removing the automatic application of const. Or, it could just delegate the application of const at compile time and hide it completely from us.
Can you back the claim that on a non const StatelessWidget is done an identity check? From the comments before it seemed that exactly that does not happen because the check would need to be recursive for the whole sub tree.
Localizations necessarily insist on non-const widgets, because any updates to the localization (say, the user changes languages or locations) need to re-process all the text.
I don’t understand what is the big deal about constness. For flutter, it’s important only that the previous “version” of the widget is identical to a new version. So, create a function - say, getMyWidget(), that returns the same (cached) widget if the language, or other rarely changed parameter, remains the same. That, for all practical purposes, would be equivalent to const, right?
(The same effect can be achieved by computing the hash, as noted earlier. In case the widget only depends on one variable, the hash computation is trivial - it’s just an identity hash code of the variable)
I don’t claim anything, It’s just what I’ve understood from the more experienced people who previously discussed this manner.
I’m far from being an expert in this area but I tried crafting an example:
→ What I noticed is that the methods are always called when the update button is pressed, while the const widgets are neither being re-created nor updated. On the contrary, the non-const widgets are creating new hashes suggesting a complete rebuild.
Now for actual measurement, I’m not experienced enough with the devtools to throw accurate results, so if someone wants to run some tests that’d be awesome.
I’m sure you are knowledgeable of the below talks/articles already, but it’s just for refs.
Rémi spoke about it here:
Flutter best-practice guide:
Graig also spoke about it on the Flutter YouTube channel:
It’s true that any updates to the localization would entail rebuilding with different text, but that doesn’t necessarily limit you to non-const widgets.
For instance, it’s possible to make a widget such as const LocalizedText(sayHi), where sayHi is a function tear-off that outputs a String given the relevant localization data.
I created this issue specifically because I read all this resources and they don’t really answer them.
You said that StatelessWidget had the advantage above functions to not getting rebuilt while this is according to your own test only the case for const ones.
Using contents of this forum for the purposes of training proprietary AI models is forbidden. Only if your AI model is free & open source, go ahead and scrape. Flutter and the related logo are trademarks of Google LLC. We are not endorsed by or affiliated with Google LLC.