Everything is a Widget, but don’t put everything in one Widget!
As a Flutter developer I’m sure you heard about this popular sentence: “Everything’s a Widget” at least once in your dev life. It’s kind of the Flutter’s catch phrase and it reveals the inner force of this extraordinarily good SDK!
When we dive in the Widget catalog, we can see a lot of small widgets doing only one job such as Padding
, Align
, SizedBox
, and so on. We create other widgets by composing them and I find this approach scalable, powerful and easy to understand.
But when I read some source code I find on the Internet or written by newly adopters, there is one thing that shock me a lot: the tendency to have huge build
methods, instantiating a lot of widgets! I find this difficult to read, understand and maintain.
As software developers, we have to remember that the real life of a software starts the first time it is released to its users. The source code of this software will be read and maintain by other people (including your future you), this is why it’s very important to keep our code simple, easy to read and to understand.
An example of “Everything in a Widget” can be found in the Flutter documentation itself. The goal of this tutorial is to show how to build this layout:
The final code serves its purpose: to show how to simply create the above layout. As we can see, there are even variables and methods to give semantics to the parts of this layout. This is a good point, as it makes the code easier to understand.
In fact it could be worse. This is the typical all in one widget version of this code I dislike:
In the second version, we have this widget with a big build
method and it’s difficult to read, understand and maintain.
Now let’s see how I would rewrite this:
Don’t you find this more readable?
🤔 What are the benefits?
I understand why tutorials don’t often do that: It takes more lines (100 lines in my example) and people might wonder why we are creating so many other widgets. Since tutorials aim to focus on one concept, it might be counter-productive to write them like this. But as a result, newly adopters may tend to put a big widget tree in their build
methods.
Let’s see what are the benefits of having a unique widget for each part of the layout:
Readability
We create one widget for each semantic part of the layout. Each widget has therefore a smaller build
method. It’s easier to read since you don’t have to scroll to get to the end of the widget.
Understandability
Each widget has a name matching its role, this is called semantic naming. By doing this, when we read the code it’s easier to map in our head what part of the code matches what we see on the app. I see two improvements in terms of understandability here:
1. When we read such a widget referenced elsewhere, we pretty much know what it does without having to see its implementation.
2. Before reading the build method of a widget with semantic naming, we already have in our mind, a rough idea of its contents.
Maintainability
If you have to replace a component, or change a part, it will be at one place only, separated from the rest of the other widgets. It will be less error-prone thanks to this practice, since the role of each widget is well defined. It will also be easier to share a part of the layout in another page within your app or even in another app.
Performances
All the previous reasons should be sufficient for you to adopt this way of creating Flutter apps, but there is another one advantage for doing this: We improve the performance of the app because each widget can rebuild separately from the others (and it’s not the case if we use methods to separate our layout parts). For example, let’s say we have to increment the number next to the red star when we click on it. In this version we could make _Likes
a StatefulWidget
and handle the increment here. When a user clicks on the star, only the _Likes
widget would be rebuild. In the first version, all the MyApp
widget would be rebuilt if we would have made it a StatefulWidget
.
This best practice is also explained in the Flutter documentation:
When
setState()
is called on a State, all descendent widgets will rebuild. Therefore, localize thesetState()
call to the part of the subtree whose UI actually needs to change. Avoid calling setState() high up in the tree if the change is contained to a small part of the tree.
Another advantage is the ability to use the const
keyword more often.Widgets can then be cached and re-used. As the Flutter documentation states:
It is massively more efficient for a widget to be re-used than for a new (but identically-configured) widget to be created.
⚡️ How to be more productive?
As you saw, by creating one widget per semantic part of the layout we write more code. We can use the stless
and stful
snippets provided by the Dart extension in Visual Studio Code, but they don’t generate a const
constructor.
For my own needs, I created new snippets, called sless
and sful
, so that I’m more productive than ever. If you want them in Visual Studio Code, you’ll have to follow this doc and add this:
💪 How to efficiently couple this practice with state management?
As you may know, there are a lot of state management solutions for Flutter. I will not list those that work well with this way of coding, but only the key concepts you should know to choose the state management that best suits your needs.
- The state needs to be accessible from one widget without having to provide it through its constructor. Otherwise you would have to pass the state through some widgets that don’t need to be aware of it.
- A widget should be able to rebuild, only when the part of the state that concerns it, changes. If it’s not the case, the widget will rebuild too many times and it can hurt the app performances.
In my opinion, the solutions that work the best are those based on InheritedWidgets or the same concept. You can have a look to Provider + X (X being a class able to notify state changes) or Maestro for example.
📕 Conclusion
I’m convinced this is a good way to write Flutter applications, and I hope you’re convinced too. If it’s not the case, I’m interested in your opinion 😉!
From now on, remember this catch phrase: “Everything’s a widget but don’t put everything in one widget!”.