As Remi has eloquently put repeatedly, it's not the functions by themselves that cause a problem, the problem is us thinking that using a function has a similar benefit to using a new widget.
Unfortunately this advice is evolving into "the act of merely using a function is inefficient", with often incorrect speculations into why this might be.
Using a function is almost the same as using what the function returns in place of that function. So, if you are calling a widget constructor and giving it as a child to another widget, you are not making your code inefficient by moving that constructor call into a function.
//...
child: SomeWidget(),
//...
is not significantly better in terms of efficiency than
//...
child: buildSomeWidget();
//...
Widget buildSomeWidget() => SomeWidget();
It is fine to argue the following about the second one:
- It's ugly
- It's unnecessary
- I don't like it
- Function does not appear in Flutter Inspector
- Two functions may not work with
AnimatedSwitcher
et al.
- It does not create a new context, so you can't reach the
Scaffold
above it through context
- If you use
ChangeNotifier
in it, its rebuild is not contained within the function
But it's not correct to argue this:
- Using a function is inefficient in terms of performance
Creating a new widget brings these performance benefits:
ChangeNotifier
within it does not make its parent rebuild upon changes
- Sibling widgets are protected from each other's rebuilds
- Creating it with
const
(if possible) protects it from parent's rebuilds
- You are more likely to keep your
const
constructor if you can isolate the changing children to other widgets
However, if you do not have any of these cases, and your build function is looking more and more like pyramid of doom, it is better to refactor a part of it to a function rather than keeping the pyramid. Especially if you are enforcing 80 character limit, you may find yourself writing code in about 20 character-wide space. I see a lot of newbies falling into this trap. The message to those newbies should be "You should really be creating new widgets here. But if you can't, at least create a function.", not "You have to create a widget or else!". Which is why I think we have to be more specific when we promote widgets over functions and avoid being factually incorrect about efficiency.
For your convenience, I have refactored Remi's code to show that the problem is not simply using functions, but the problem is avoiding creating new widgets. So, if you were to place the widget-creating code in those functions into where the functions are called (refactor-inline) you have the exact same behavior as using functions, but without using functions! So, it's not using functions that's the problem, it's the avoidance of creating new widget classes.
(remember to turn off null safety as the original code is from 2018)
Here are a few interactive examples on Dartpad that you can run
yourself to better understand the issues:
https://dartpad.dev/1870e726d7e04699bc8f9d78ba71da35 This example
showcases how by splitting your app into functions, you may
accidentally break things like AnimatedSwitcher
Non-function version: https://dartpad.dev/?id=ae5686f3f760e7a37b682039f546a784
https://dartpad.dev/a869b21a2ebd2466b876a5997c9cf3f1 This example
showcases how classes allow more granular rebuilds of the widget tree,
improving performances
Non-function version: https://dartpad.dev/?id=795f286791110e3abc1900e4dcd9150b
https://dartpad.dev/06842ae9e4b82fad917acb88da108eee This example
showcases how, by using functions, you expose yourself to misusing
BuildContext and facing bugs when using InheritedWidgets (such as
Theme or providers)
Non-function version: https://dartpad.dev/?id=65f753b633f68503262d5adc22ea27c0
You will find that not having them in a function creates the exact same behavior. So it's adding widgets that gives you the win. It's not adding functions that creates a problem.
So the suggestions should be:
- Avoid the pyramid of doom at any cost! You need horizontal space to code. Don't get stuck at the right margin.
- Create functions if you need, but do not give parameters to them as it's impossible to find the line that calls the function through Flutter Inspector.
- Consider creating new widget classes, it's the better way! Try Refactor->Extract Flutter Widget. You won't be able to if your code is too coupled with the current class. Next time you should plan better.
- Try to comment out things that prevent you from extracting a new widget. Most likely they are function calls in the current class (
setState
, etc.). Extract your widget then, and find ways of adding that stuff in. Passing functions to the constructor may be ok (think onPressed). Using a state management system may be even better.
I hope this can help remind why we prefer widgets over functions and that simply using a function is not a huge problem.
Edit: one point that was missed in this whole discussion: when you widgetize, siblings don't rebuild each other anymore. This Dartpad demonstrates this: https://dartpad.dartlang.org/?id=8d9b6d5bd53a23b441c117cd95524892