Improve your Flutter app performance: split your widgets

setState … setState … setState … Once you code an app of medium complexity, it becomes very important to think about the performance impact of your Flutter widgets.

Thinking about app performance is about being a good app citizen. Not only the app will be smoother for the user, but also it will drain less battery. And we all know battery is still the single point of failure for smartphones!

The official Stateful Widget doc has some very helpful information on performance considerations. Here, I will focus on the first item: “Push the state to the leaves”.

In this code tutorial, we will set up a screen with a list of shopping items. Each item displays a title and a picture. Some items actually have several pictures available, so we will loop through them in the display, with the picture changing for that item every 2 seconds.

Setting up the app

To follow the code tutorial, create a new app as follows.

If you’re unsure how to set up a Flutter app, check out Getting started with Flutter official tutorial.

Firstly, we create a Material app in main.dart, which will launch the HomePage widget.

 

Secondly, we create home_page.dart. It will eventually show a list, but it is not implemented yet.

 

Setting up the app architecture

I like to use MVP for architecture: simple enough to be understood by developers of all backgrounds, yet scalable and easily maintainable. There are many ways to architect an app, I don't claim that MVP is the best! If you're curious about app architecture, check out the Flutter Architecture Samples.

I use “contracts” when using MVP. Contracts are abstract classes that define the methods for each component of the feature, ie View, Model, and Presenter.

Note: You do not need to set up contracts for applying MVP, but I find it very helpful to have them. I define them all in one file, and this makes it easy to understand what a feature does.  When I worked as an Android DPE at Google, I worked on the first release of the Android Architecture Blueprints project and we decided to use contracts. I have used contracts on all my professional projects since, and I haven’t looked back: this really helps make a complex app maintainable!

So let’s set up our contract for this feature, by creating a new file home_contract.dart.

 

The only user action per se is the user starting the app to show the screen, ie view displayed. This is async, because it will update the view based on async data responses from the Model.

 

In terms of data, we are concerned with 2 things: getting the list of items (ie titles), and getting an image to show for each item.

 

Finally, the view has 3 possible states: loading of items in progress, display items,  and display images (per item). We ignore error scenarios for this code tutorial. The first state is the initial state of the view.

Implementing the MVP contract

Now, we create Model and Presenter classes that implement the abstract classes. The View abstract class is implemented in HomePage.

Firstly, let’s start with home_presenter.dart. The Presenter has a reference to the View and the Model, as it acts as a “coordinator” between View and Model.

 

Secondly, let’s set up home_model.dart. For this code tutorial, we will load the images from assets, and we hard code the list of items.

 

To load images from assets, we create a new folder assets (on same level as lib folder). We then add 5 images files to it: 1 2 3 4 5 (click on each link, and right click on picture to save it in your newly created assets folder). We also need to amend the pubspec.yaml file.

 

Finally, HomePage itself will implement the View, and will create the Presenter when initialised.

 

At this point, the app shows the list of items, and, when loaded, the first image for each item. But we already notice an issue: the whole page gets rebuilt every time an item loads its image. From a user point of view, this is the right thing to do: we show images as soon as they are available. But for performance, it’s clearly wasteful. And we’re not even feature complete! It’s going to get worse…

 

 

Looping through the images

To loop through the image, we create a new method in the presenter, and we amend the model to return the next image instead of the first image.

The new presenter method _loopThroughImages() waits for 2 seconds, calls the model to get the item image for each item, updates the view for each item, then calls itself. As we now add a loop in the presenter, we need to add a way to stop it when disposing of the view.

Firstly, we amend home_contract.dart.

 

Secondly, we amend home_presenter.dart.

 

Finally, we amend home_model.dart.

 

OK, it works, but it’s messy. The whole view gets rebuilt every time one item image is displayed.

This is the kind of implementation we may write as a first stab, when prototyping a UI for example, but the HomePage gets rebuilt way  more often than is necessary. So let’s push the state (the images) to the leaves (to their own stateful widget).

Refactoring widget to avoid rebuilding the whole page each time we loop through images

When we stop to think about when the HomePage widget gets rebuilt, we reach the conclusion that we should have a separate widget for each item view. But all the data comes from the same repository/model, so how do we achieve this?

Let’s go back to our contract, and split the view into 2: ItemsListView and ItemView. We amend home_contract.dart, by replacing abstract class View  with the code below.

 

Then, we amend home_presenter.dart. It is now a singleton that will be used by several views, and it has methods for views to add themselves to it.

 

Then, we create a new widget, in the file item_view.dart, and move across the _buildItemView() method from HomePage to it. This widget implements ItemRowView from the contract.

 

Lastly, we amend home_page.dart to use the new widget, as well as implements the ItemsListView instead of View. We also delete methods _buildListRow and showImageForItem.

 

Performance profiling

All this theory about why we refactor is all nice and good, but have we really improved performance? Let’s check the Android Monitor.

The first thing to note is that performance stats vary quite a bit, on the same device, so it’s not an exact science.

But one thing is constant between all tests: CPU usage every 2 seconds (the loop) is higher before the refactoring. Not by a lot, or course, but it is around the 10% line much more often than after refactoring.

CPU before refactoring (10% line shown in grey):

Before code refactoring
Before code refactoring

CPU after refactoring (10% line shown in grey):

After code refactoring
After code refactoring

 

Enjoying this code tutorial? Support the blog!

Options
 

What next?

Generally speaking, if you remember that “calling setState will rebuild the whole widget”, you’re on a good path for improving your app performance. There are other considerations of course, such as making sure you use async code for I/O operations.  For further help on improving or checking the performance of your app, check out  the Performance Profiling  section in the official doc.

 

 

 

Android app developer with 8 years experience, ranging from start ups to large tech (Google) and non tech (British Airways) companies. Currently freelancing and learning Flutter. LinkedIn natalie ( at ) cogitas (dot) net

Author: Natalie Masse Hooper

Android app developer with 8 years experience, ranging from start ups to large tech (Google) and non tech (British Airways) companies. Currently freelancing and learning Flutter. LinkedIn natalie ( at ) cogitas (dot) net

Leave a Reply

Your email address will not be published. Required fields are marked *