Top 7 best practices in Flutter

Felix Gomez
7 min readNov 20, 2022

--

Best practices to build better, less expensive in the long-term, and more robust applications

Photo by Mika Baumeister on Unsplash

Flutter is an open-source framework developed by Google and it is probably the most loved one to build cross-platform applications nowadays. It was released in 2017 and since then the community has grown so much that you can find enough help by just Googling.

The concepts and tips you will find in this article are a part of what I have learned since I started using Flutter 2.0 to develop my applications for iOS and Android. However, the practices in this article were designed for Flutter 3+ (but all of them apply to Flutter 2+ too).

1. 🌗 Separate concerns

It’s very easy to mix concerns in Flutter because you have the logic and presentation layers very close together, which makes the code harder to maintain in the long term, more prone to errors, and it is easier to Repeat Yourself.

However, there are multiple techniques you can use to split concerns:

  • Create functions to manage conditionals instead of using ternary operators inside a Widget directly
  • Move your calls to APIs into a different folder/file, or even better create an SDK for them, so you can reuse the logic in other projects too
  • Implement an architectural pattern like BloC, repository, or MVVM; it doesn’t matter which one you choose as long as you have some pattern that can give your code structure

For example, instead of managing conditionals like this:

Example of the ternary operator inside the build method

Do the following:

Example of the ternary operator outside of the build method

2. ⚒️Create widgets to reuse code

Widgets are the chore of Flutter, they are everything, and everything works as a Widget under the hood. They are probably the reason why developing in Flutter is so easy.

To create a reusable widget there are two ways:

  1. Using a method that returns a widget; this one should be avoided as it makes the returned widget rebuild on every call to the build method
  2. Creating a class that extends from StatelessWidget or StatefulWidget; both are preferred, because Flutter attempts to cache their content, however, the stateful widget should only be used when you need to mutate states

In general, use either a helper function or a class to avoid repeating yourself and allowing your code to look cleaner and easier to maintain, but always prefer to use a class for better performance (and testability).

Avoid doing the following:

Using a helper method to build a widget that returns a card

Do the following instead:

Using a class to build a widget that returns a card

3. 🔎 Pay attention to logic inside the build method

It is a common good and safe assumption that at every frame, a Widget would call the build method so that Flutter knows what to render. It is not usually that critical (unless your code is doing something really weird), but still the build method is called quite often, making anything we do inside the method be rebuilt again unless is cached.

Most of the Widgets are already cached, but business logic, futures, and heavy tasks will be executed again inside the build method, causing the app to feel laggy and slow, hence, damaging the app’s user experience.

Three simple ways to tackle this problem are:

  • Move the business logic out of the build method (it is separating concerns again)
  • Use const whenever possible, because the widget will attempt to efficiently only rebuild the non-constant content, making the rebuild faster and the app feels better, hence, improving the user experience
  • Make sure to only hit API endpoints outside of the build method; I’ll explain how FutureBuilder can be used efficiently for that in the next section

4. 🔮 Do not obtain futures inside FutureBuilder

FutureBuilder is an amazing widget to show the different states of a Future, like waiting, successful, and error responses, by rendering a specific widget depending on the case. It could be used like this (avoid):

FutureBuilder calls the future every time the build method is executed

The catch is that FutureBuilder is a widget, hence lives inside the build method, which as explained before is called multiple times during the screen lifetime, making it recall the future at every time. It could increase server costs if it is your API, or it could even be a third-party API that has a cost associated with every hit to an endpoint. Moreover, it would make your app feel slower and the user experience would be crushed.

Nonetheless, the solution is simple: you just have to call the future outside the build method and only retrieve the content with the FutureBuilder. It will still have different states, but the Future will be solved only once. The next picture is an example of the proper implementation based on the previous piece of code:

FutureBuilder calls only the result of the future, it doesn’t do the call itself

5. 🧱 Avoid heavy logic in setState()

Stateful Widgets have this awesome method to tell the widget that something in the code has changed and that it needs to rebuild itself; the setState() method.

If you want to modify a variable and let the UI know of the change, you have to do it inside setState, however, as it is in charge of telling the widget to rebuild itself, it should only have the change needed or the UI can become laggy and slow (especially because setState is a synchronous method).

The best practice here is to avoid having heavy logic inside the call to setState(), but instead, do the heavy lifting outside and only indicate the exact change to be shown in the UI through setState(). For example:

Bad and good examples of setState method use

6. 😶‍🌫️ Avoid unnecessary containers

Developers that come from web development tend to use Container Widgets in Flutter as they use divs in HTML. I am here to tell you: stop doing it. Containers need a lot of stuff to work under the hood, they are heavy and can easily decrease the FPS and make the app laggier if you use them just to wrap other Widgets. Don’t get me wrong, they are needed a lot of times, and they exist for a reason, however, the idea here is to only use them when you truly need them, because wrapping a widget in a Container with no other parameters set has no effect and makes the code needlessly more complex.

Moreover, when you think you need a Container, first look up the Widget Catalog to see if there is another widget that already does what you need. You will probably get surprised at what comes already built in Flutter.

The first example, using a Container when it is not needed:

Use a Container when it is not needed

The second example, using a Container when there is another widget more efficient:

Use a Container when SizedBox is more efficient

7. 🪪 Always use data types

Dart allows us to declare dynamic data types with var because the data type called dynamic takes care of those values; which is useful when you don’t know the data type of the variable you have (usually when you get data from an API). However, dynamic data types can become a robust nightmare and make your code more prone to runtime errors, while using proper data types you get help from the compiler and probably the IDE too.

My bits of advice for this is to avoid using dynamic data types and var declarations, force yourself to understand the meaning of the data, and if you have the option to not put the type, put it anyway.

An especially useful case for the last piece of advice is when you use FutureBuilder and AsyncSnapshot without the type:

Both the IDE nor the compiler won't know what data the snapshot is going to retrieve, which is a common cause of runtime bugs. However, if you add the data type, they can know and advise you of errors at compile time:

As we can see, the data type expected for the snapshot is a String, and it’s even throwing an error because the future is not returning a String. This simple red highlighting could be the difference between a long and short night. Always use data types! 😇

Stay tuned for more articles like this one. If you would like me to write about something, in particular, let me know in the comments.

I am a lover of learning, so I would love to read your opinions and any feedback to continue my learning path, please go and leave a comment!

Thanks for reading this article and let’s create a better world together! ❤

Follow for more and share it if you liked it 😊

--

--

Felix Gomez

Mechatronics Engineer | 12+ years programming | Pianist | Scientist | I know one or two things about everything, and I like to share my knowledge with everyone