TOP 10 performance & optimization tips in Flutter
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.
- Lazy loading
- Use the appropriate widget
- Avoid using
setState
excessively - Use
const
andfinal
where possible - Use caching
- Monitor performance
- Use the
Offstage
widget - Use
WidgetsBinding.instance.addPostFrameCallback
- Use
AutomaticKeepAliveClientMixin
- 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.