How refactoring improve readability, maintainability and performance optimization of your Flutter application

Jonathan Monga
Flutter Community
Published in
7 min readFeb 10, 2020

Introduction

We all agree that the widget tree is what you get in a UI and agree that it’s all about widgets in Flutter, so you nest your widgets in each other. Whether your UI is simple or complex, when your UI is simple, even when you come back to your code a few weeks later, it’s easy for you to re-read your code, and your application is more efficient because it there is less shown, but when your user interface is more complex, it is that which pushes you to nest a large number of widgets, your code becomes less readable, less maintainable and your application becomes less efficient.

I know, for the beginner, it is quite easy not to have the culture of refactoring your codes, once moving on to other things, the beginner is content to nest, nest and nest his widgets, this is what produces a the very deep widget tree. this is a very recurring problem that we face for the new Flutter developer like me. Well, we exposed the problem, but how do we avoid it? How to code in a way is not to fall into the hell of the very deep widget tree?

Several people have talked about this before me, but I think it’s worth the cost to come back to it again, Then the answer to this problem that often haunts us is refactoring code, well, you have what you want, so don’t delay refactoring your code, using different techniques that I will show you in the rest of my article.

Before showing you how to refactor your code, let’s use the code for this UI:

this beautiful UI comes from https://github.com/JideGuru/weather_neumorphism_ui
This beautiful UI comes from https://github.com/JideGuru/weather_neumorphism_ui

This is not an activity of malice. Nor do I think that I am so much better than Olusegun Festus Babajide that I somehow have a right to pass judgment on his code. Indeed, if you were to find some of my code, I’m sure you could find plenty of things to complain about.

No, this is not an activity of nastiness or arrogance. What I am about to do is nothing more and nothing less than a professional review. It is something that we should all be comfortable doing. And it is something we should welcome when it is done for us. It is
only through critiques like these that we will learn. Doctors do it. Pilots do it. Lawyers do it. And we programmers need to learn how to do it too. One more thing about Olusegun Festus Babajide: Olusegun Festus Babajide is more than just a good Flutter Developer.

Olusegun Festus Babajide had the courage and good will to offer his code to the community at large for free.
He placed it out in the open for all to see and invited public usage and public scrutiny. This
was well done!

That’s ok for the justification, here is the code now:

home.dart

So let’s see how to get it all in order.

1. Refactoring with a method

I think you have already seen this technique somewhere without realizing it. This technique consists of returning just a widget to the call of a method by its signature. Assuming that everything is widget with Flutter, so any class that participates in the composition of the UI is descended from a Widget class, the return type of the method can be either a widget class or a specific class for example the container class, row, column etc.

Follow me right here, Widgets in the method can rely on the parent widget’s BuildContext instance or object. And alas, this is where the problem comes from, Remember, the BuildContext object that knows the position of the widget in the widget tree.
The fact that they depend directly on the main BuildContext object, this forces the method to reassemble, recreate or redraw its widgets when the parent widget is redraw and if the method alone calls other methods which also depend on the BuildContext of the parent widget, it creates a side effect, all its methods will also redraw their widgets the number of times the parent widget will be redraw, in any case it is not the behavior we expected after the refactoring.

With this approach, we gain of course in readability and maintainability by separating widgets in section, but for performance optimization it is not famous. When the number of widgets increases, Our UI will tend to slow down, during a configuration change, for example a rotation of the screen.

Here is an example of two methods :

build_leading_column.dart
build_row.dart

We use Visual Studio Code as code editor an we refactor with this steps :

  1. Open the any .dart file,
  2. Place the cursor on the first widget and right-click, in my case on Row, Container or Column.
  3. Select Refactor >Extract Method
  4. In the Extract Method dialog, we enter _buildRow for the method name. Notice the underscore before build; this lets Dart know that it is a private method.
  5. The Row widget is replaced with the _buildRow() method. Scroll to the bottom of the code, and the method and widgets are nicely refactored.
  6. Continue and refactor the other Rows, Colums, Containers and Stack widgets.

This approach increases the readability of the code, the main parts of the widget tree are separated into very simple methods, the interest of this technique is pure and simple the readability and maintainability of the code, in return you lose the optimization performance. If you want to see all code, go below you will see the reference link.

2. Refactoring with a local variable

Similar to the one we saw above, however refactoring with a local variable consists of initializing a widget with a final variable. Here too you separate the main parts of the widget tree into sections, which increases the readability and maintainability of the code.
Since in this case too, our widgets although being final variables, they depend on the BuildContext of the parent widget, when framework redraws the parent, and the local variables are also redrawn.
This increases readability, maintainability, your widget tree becomes shallower, but does not optimize performance.

Here is an example of the refactor code with a constant:

We use Visual Studio Code as code editor an we refactor with this steps :

  1. Open the any .dart file,
  2. Place the cursor on the first widget and right-click, in my case on Row, Container or Column.
  3. Select Refactor > Extract Local Varialble
  4. In our case, we give rowConstant for the local variable name. Notice that we declare the variable as final, to tell Dart that it is simply a constant.
  5. The Row widget is replaced with the rowConstant final variable. Scroll to the top of the code, and the local variable and widgets are nicely refactored.
  6. Continue and refactor the other Rows, Colums, Containers and Stack widgets.

3. Refactoring with a widget class

This technique allows you to isolate a subtree of widgets in a class extending the StatelessWidget or StatefullWidget class. It also allows you to create reusable widgets, and separate them into the same or different Dart files, which you can import or use anywhere in your application. Warning ! All the constructors of these classes will have to start with the keyword const, thanks again to Dart. Begin the declaration of a constructor with const, come back to tell Dart to cache and reuse the widget, although the other widgets can be redrawn.
When you are going to create an object of this class, don’t forget to start with the keyword const once more. By doing this the widget will not be rebuilt when the others change their state in the widget tree. If you omit the const keyword, the consequence is that the number of times that the parent widget will be rebuilt or redraw, well, our widget will suffer the same fate, so be careful.

The widget class counts on its own BuildContext, not on that of its parent as when approaching refactoring with a constant or method. The BuildContext is responsible for managing the position of the widget in the widget tree.

Now let’s see a small example of refactoring code using this technique:

padding_widget.dart

We use Visual Studio Code as code editor an we refactor with this steps :

  1. Open the any .dart file,
  2. Place the cursor on the first widget and right-click, in my case on Padding, Row, Container or Column.
  3. Select Refactor > Extract Widget
  4. In our case, we give PaddingWidet for the class name.
  5. The Padding widget is replaced with the PaddingWidet class. Scroll to the bottom of the code, and the class and widgets are nicely refactored.
  6. Continue and refactor the other Padding (PaddingWidget class), Rows (RowAndColumnWidget class) widgets.

Sorry, if there has been too much information to digest, I conclude by saying that not only do you gain in readability and maintainability, performance optimization is improved on a large scale, so when the parent is redraw, not all widget classes will be redrawn. They are built only once.

Conclusion

In this article, you learned that the widget tree is the result of nested widgets. As often the number of widgets tend to increase, the widget tree expands rapidly and reduces the readability and manageability of the code.
This is called the entire widget tree. To improve readability and manageability of the code, you can separate widgets from its own widget class, creating a shallow tree of widgets. In each application, you should strive to keep the widget tree shallow.
By refactoring with a widget class, you can take advantage of the reconstruction of Flutter subtrees, which improves performance.
Thank you for reading me, and I’m open to any comments you may give me.

--

--

Jonathan Monga
Flutter Community

Java dev | Dart Dev | Android developer | Flutter developer | Speaker.