Photo by Karl Pawlowicz on Unsplash

TOP 10 performance & optimization tips in Flutter

Slawomir Przybylski
7 min readJan 16, 2023

--

Flutter is a popular open-source mobile application development framework created by Google. It is used to develop applications for Android, iOS, Linux, Mac, Windows, and the web. Flutter uses the Dart programming language and provides a rich set of customizable widgets for building high-performance, visually attractive user interfaces.

However, like any framework, there are ways to optimize the performance of your Flutter app. In this article, we will discuss several performance tips for Flutter developers to help improve the performance of their apps.

  1. Lazy loading
  2. Use the appropriate widget
  3. Avoid using setState excessively
  4. Use const and final where possible
  5. Use caching
  6. Monitor performance
  7. Use the Offstage widget
  8. Use WidgetsBinding.instance.addPostFrameCallback
  9. Use AutomaticKeepAliveClientMixin
  10. Optimize your images

Wait! It is not the end, I know you expect practical knowledge and you are in the perfect place to gain it. Let’s dig deeper.

Lazy loading

Lazy loading is a technique that allows you to load data or resources only when they are needed. This can greatly improve the performance of your app by reducing the amount of data that needs to be loaded at startup. For example, if you have a long list of items that are not visible at first, you can use lazy loading to only load the items that are currently visible.

Take a look at this sample code.

ListView.builder(
itemCount: items.length,
itemBuilder: (BuildContext context, int index) {
return items[index];
},
)

In the above example, only the items that are currently visible are loaded, which can save a significant amount of memory and improve the overall performance of your app. It is important to use the ListView.builder instead of the basic constructor!

Use the appropriate widget

Flutter provides a wide range of widgets that can be used to build user interfaces. However, not all widgets are created equal. Some widgets are more performant than others.

For example, the ListView widget is more performant than the SingleChildScrollView widget when working with large lists of items.

Similarly, the Opacity widget is more performant than the AnimatedOpacity widget when applying opacity changes. If you do not need the animation, choose Opacity for sure.

Try to avoid any Clip widget. They are really resource-intensive. Do not worry, if you are not able to replace Clip widget. Just be aware of its side effects.

//Using ListView
ListView.builder(
itemCount: items.length,
itemBuilder: (BuildContext context, int index) {
return items[index];
},
)

//Using SingleChildScrollView
SingleChildScrollView(
child: Column(
children: items,
),
)

Be sure to use the appropriate widget for the task to ensure optimal performance.

Avoid using setState excessively

The setState method is used to notify the framework that the internal state of a widget has changed and needs to be rebuilt. However, using this method excessively can lead to poor performance.

To avoid this, try to minimize the number of times you call the setState method and only call it when it is absolutely necessary.

On the other hand, you can use one of the state manager libraries. Flutter’s community has prepared plenty of those. The most popular is BLoC.

Use const and final where possible

Using the const and final keywords can greatly improve the performance of your app. When a value is declared as const, it is evaluated at compile-time instead of runtime. This means that the value is known at compile-time and does not need to be recalculated at runtime. Similarly, when a value is declared as final it can only be set once and does not change thereafter. This means that the framework does not need to constantly check for changes, leading to improved performance.

final items = ["Item 1", "Item 2", "Item 3"];

In this example, the variable items is declared as final, which means its value cannot be changed. This improves performance because the framework does not need to check for changes to this variable.

I highly recommend using the linter package, with either default or custom settings. It will help you to remember about keywords like const or final and improve your code overall.

Use caching

Caching is a technique that allows you to store data in memory so that it can be quickly retrieved without the need to recalculate or refetch it. This can greatly improve the performance of your app by reducing the number of times data needs to be fetched or calculated. For example, you can cache images or other resources that are frequently used in your app.

class MyImage extends StatelessWidget {
final String imageUrl;

MyImage({required this.imageUrl});

@override
Widget build(BuildContext context) {
return Image.network(imageUrl, cacheWidth: 200);
}
}

In this example, the MyImage widget uses the Image.network method to load an image from a URL. The cacheWidth parameter is used to cache the image at a specific width, which can be useful for reducing the amount of time it takes to load the image and improve the overall performance of your app.

You can apply different approach for caching data, either your own or already built one. pub.dev provides many packages for this purpose. In my opinion, you should definitely check cached_network_image.

Monitor performance

Have you ever wondering why your app works so slowly or drops FPS? Monitoring the performance of your app is essential for identifying and resolving performance issues. Flutter provides several tools for monitoring performance, including the debugDumpApp() and debugDumpRenderTree() methods, which can be used to print the widget tree and the rendering tree, respectively. Additionally, the Flutter DevTools provide a wide range of performance-related information, including CPU usage, memory usage, and frames per second.

Remember to take a look at your favourite IDE extensions. You can find many plugins to monitor the app performance, either you use Android Studio, Visual Studio Code or anything else.

Use the Offstage widget

The Offstage widget allows you to hide a widget without removing it from the widget tree. This can be useful for improving performance because the framework does not need to rebuild the hidden widget.

Offstage(
offstage: !showWidget,
child: MyWidget(),
)

In this example, the Offstage widget is used to hide the MyWidget widget when the showWidget variable is false. This improves performance by reducing the number of widgets that need to be rebuilt.

Have you ever wondering what is the difference between Offstage, Opacity and Visibility widget? Here you can find short explanation.

In Flutter, the Offstage widget is used to hide a child widget from the layout while it is still part of the tree. It can be used to conditionally show or hide a child widget without having to rebuild the entire tree.

The Opacity widget is used to control the transparency of a child widget. It takes a single value between 0.0 and 1.0, where 0.0 is fully transparent and 1.0 is fully opaque. However, it’s important to note that it may impact performance, so use it only when necessary.

The Visibility widget is used to control the visibility of a child widget. It can be used to conditionally show or hide a child widget without having to rebuild the entire tree.

All three widgets are used to control the display of child widgets, but they do it in different ways. Offstage controls the layout, Opacity controls the transparency, and Visibility controls the visibility.

Use WidgetsBinding.instance.addPostFrameCallback

In some cases, we need to perform some action after the frame is rendered. Neither do not try to use any delay function, nor create custom callbacks! We can use WidgetsBinding.instance.addPostFrameCallback method to do that. This callback will be called after the frame is rendered and will improve the performance by avoiding unnecessary rebuilds.

WidgetsBinding.instance.addPostFrameCallback((_) {
//Perform the action here
});

Use AutomaticKeepAliveClientMixin

When using ListView or GridView, the children can be built multiple times. To avoid this, we can use AutomaticKeepAliveClientMixin for the children widgets. This will keep the state of children widgets alive and will improve the performance.

class MyChildWidget extends StatefulWidget {
@override
_MyChildWidgetState createState() => _MyChildWidgetState();
}

class _MyChildWidgetState extends State<MyChildWidget> with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;

@override
Widget build(BuildContext context) {
return Text("I am a child widget");
}
}

In this example, the MyChildWidget class is using the AutomaticKeepAliveClientMixin mixin and the wantKeepAlive property is set to true. This will keep the state of the MyChildWidget alive and prevent it from being rebuilt multiple times, resulting in improved performance.

Optimize your images

Large images can greatly impact the performance of your app. To improve performance, you should optimize your images by reducing their size and resolution. This can be done using image editing software, such as Adobe Photoshop or GIMP. Additionally, you can use plugins like image_optimizer or flutter_image_compress to optimize images during the build process.

If you are looking for an online solution, try tinyJPG, a popular image optimizer. Alternatively, you can use software like file-converter.org, one of my favorites, to compress your images. Be sure to check it out!

Image.asset(
'assets/images/large_image.jpg',
width: 200,
height: 200,
fit: BoxFit.cover,
)

In this example, the Image.asset method is used to load an image from the assets folder. The width and height properties are used to scale the image down to a specific size, and the fit property is set to BoxFit.cover, which will crop the image to fit the specified size.

By following these tips, you can improve the performance of your Flutter app and provide a better user experience for your users. Remember to monitor the performance of your app regularly and make adjustments as necessary to ensure that it is running at its best.

--

--

Slawomir Przybylski

Highly experienced Flutter and Android developer with over 6 years of experience in the industry