Flutter Performance: Top 10 Best Practices
Performance is a crucially important aspect of any modern mobile app. Skipped frames and freezes hurt app’s usability and leave a bad impression on users. And if a slow e-commerce app is just a bad app, a stuttering game is a completely unusable app. To feel smooth, the app should maintain 60 FPS (frames per second) most of the time, or in other words, change frames every 16.66 milliseconds. Modern apps use many visual elements at once, making it a heavy load on the device’s hardware. Improving the app’s code and architecture is the main way to make it perform better. Surf has an extensive experience of developing apps with Flutter — from mobile banking and e-commerce stores to streaming platforms and corporate apps. Read our article to learn about the best practices for improving your Flutter app performance.
Flutter and other platforms
Developed by Google, Flutter is a cross-platform framework that runs on Dart coding language. As well as being one of the most popular cross-platform technologies on the market, it is also one of the most powerful in terms of performance. Let’s briefly overview how its performance compares to other mobile app technologies.
Flutter vs React Native performance
Multiple third-party tests show that Flutter and React Native provide stable 60 FPS during standard scrolling. However, React Native uses more device’s battery power and memory, and the framework shows poorer performance (drops to minimal 7 FPS vs 19 FPS for Flutter) when it comes to heavy animations with rotations, scaling and fade. So, Flutter can be named a winner. For more details read our comparison of Flutter and React Native.
Flutter vs Ionic performance
Xamarin vs Flutter performance
As we tell in our Xamarin vs Flutter overview, both frameworks offer nearly native performance. However, Xamarin performance depends highly on the type of Xamarin framework used. If Xamarin.Android and Xamarin.iOS with more platform-specific code provide great performance, Xamarin.Forms that shares more code across platforms, performs worse. Also, with Xamarin, a developer might need to develop many UI components separately for iOS and Android, meaning that the development of UI-heavy apps might be significantly slower.
Flutter vs native performance
When it comes to native platforms (apps written on Swift for iOS and Kotlin for Android), their performance tends to be better than of any cross-platform technology. The reason why in contests ‘Flutter vs Kotlin performance’ or ‘Flutter vs Swift performance’ the native platform will always be a winner: its code is compiled in the same format as the device’s native one, and it uses almost 50% less memory in virtually similar apps. However, thanks to the AOT (ahead of time) compiler and highly-optimized Skia rendering engine, the performance of most Flutter apps is usually on par with native ones, so users won’t notice much of a difference. To learn what better suits your project: Flutter or native frameworks, read our dedicated article.
How to measure Flutter performance
In general, it is recommended to measure performance in the Profile mode using a real device (preferably a low-end one) instead of an emulator. Performance testing in Flutter apps can be done in several ways, since the framework provides a wide array of options for tests and performance measurement. Below, we overview the most popular ones.
One way to conduct Flutter performance tests is to use the Performance widget. The overlay widget displays two graphs on top of the app. The top ‘GPU’ graph shows the raster thread performance, in other words, communication between the app’s layer tree and the device’s GPU. Despite being named ‘GPU’, the graph shows how CPU resources are utilized. The lower ‘UI’ graph shows the UI thread, which includes the written code executed by Flutter’s framework.
If a frame is shown for more than 16.6 milliseconds (meaning the app’s performance drops below 60 FPS), the blue graph will drop down and reveal white background and you’ll see a red vertical line. If this occurs in the ‘FPS’ graph, that means the visual elements on the screen are too complicated to render in time, if it occurs in the ‘UI’ graph — the Dart code is too expensive to execute in time. If both graphs display a red bar, start by examining the UI thread for possible issues.
The performance overlay can be launched in several ways, including:
- The Flutter inspector. Launch the app in the Profile mode, open DevTools and toggle the Inspector view. There you’ll find the Performance Overlay button.
- The command line. Use flutter run –profile command and then the P key to turn on the performance widget.
Learn more about Flutter performance profiling from the official Flutter documentation.
With the performance view, which is accessible from DevTools, you can learn about the app performance via three tools.
- Flutter frames chart
- Timeline events chart
- CPU profiler
Data from the Performance view can be exported and imported. We also suggest checking out Flutter documentation to learn more about the Performance view capabilities.
The app performance can also be measured with Flutter performance benchmark tests, which are done via integration testing. The tests show such metrics as startup time, battery usage and jank (skipped frames).
How to optimize Flutter performance
Avoid costly build method
The repetitive and costly build() method consumes excessive CPU power. This can happen if you use a large Widget with a large build() function. It is better to split such a Widget into smaller ones based on encapsulation and how they change. For example, localize the setState() call to the part of the subtree (in other words, a child of a node) that requires UI changes. If setState() is called too high in the tree, it rebuilds all descendent widgets of the tree.
Use const Widgets
Avoid excessive widget rebuilding by using const Constructor to make the setState remain in the constant state.
Prefer lazy methods for lists and grids
Use of Lazy Lists and Lazy Grids is preferable for any large UI elements. This way, only the part that is visible on the screen is rendered when the app is launched.
Use Opacity only when necessary
Opacity Widget makes the widget rebuild every frame, which may cause Flutter performance issues, especially if there’s animation present. Opacity applied directly to an image consumes fewer resources compared to the Opacity widget. Also, go for TransparentImage, AnimatedOpacity, FadeInImage or FadeInTransition widgets instead of the Opacity widget to improve performance.
Avoid calls to savelayer
Calling saveLayer() is taxing on the hardware and should be avoided when possible. Widgets that can potentially trigger saveLayer() operation are: Text (if an overflowShader is used); Chip (if disabledColorAlpha != 0xff); ColorFilter and ShaderMask. Also, to avoid calling saveLayer() tweak the borderRadius widget’s property to round corners of a rectangle, instead of using a clipping rectangle.
Build and render in 16ms
If you notice the app’s skipping frames, review which frames take more than 16 milliseconds to build and render. Because there are separate threads for building and rendering, to achieve 16ms or less in total, you should aim at each frame to be built in 8ms or less and rendered in 8ms or less.
If frames are rendered in less than 16ms it won’t make a significant visual change for users, but might improve battery life and cause less heating of the device. Read more on rendering best practices on Flutter documentation portal.
Choose SizedBox instead of Container
To create a box or whitespace of specified dimensions use the SizedBox widget, which is less taxing on the system, compared to the Container widget.
Use pre-built child subtree with AnimatedBuilder
If you use the AnimatedBuilder widget, it’s not recommended to put a child of a node that doesn’t depend on animation in the builder function, because it will rebuild the subtree on every tick of animation. Better build the subtree once and pass it as a child parameter to the widget.
Do not use ListsView for long lists
If a list is not fully present at once on a screen, create it with the ListView.builder constructor, rather than ListView() or Column(). This way, the constructor will gradually render items as they’re scrolled on the screen, instead of creating them at once and negatively impacting performance.
Say ‘no’ to splitting widgets into methods
If you have a large building method with multiple nesting levels, the first idea might be to split it into separate methods. However, this will force Flutter to rebuild all child widgets every time the parent widget rebuilds, even if some of them are completely static. To prevent wasting CPU power on repetitive rebuilding, split complex widgets into smaller StalessWidgets.
To sum things up
Flutter performance optimization has a good reputation and the framework is known among developers for how it provides nearly native performance even in apps with heavy visual effects. One of the examples is an app developed by Surf for The Hole video-streaming platform — despite our doubts, Flutter handled animations easily and provided smooth and responsive playback controls. Nevertheless, it is always recommended to follow best performance practices to reduce risks of app stuttering, errors or skipped frames to the minimum. Hope our short guide will help you in achieving your development goals!
If you’re currently thinking about building your mobile app, why not handle the task to professionals? We at Surf have extensive experience building apps for various businesses and will be glad to work on your project. Fill in the short form, and we’ll estimate your app shortly.