Flutter best practices for Improve Performance
Does flutter performance fast enough compared to other hybrid platforms? The answer is Yes it is, but out of that thinking let’s jump on some amazing practices for performance and optimization.
1. Use Widgets over Functions
Don’t use it like thisUse it like thisWidget _buildFooterWidget() { return Padding( padding: const EdgeInsets.all(8.0), child: Text('This is the footer '), ); }
class FooterWidget extends StatelessWidget { @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(8.0), child: Text('This is the footer '), ); } }
Flutter wouldn’t have StatelessWidget if a function could do the same thing.
Similarly, it is mainly directed at public widgets, made to be reused. It doesn’t matter as much for private functions made to be used only once — although being aware of this behaviour is still good.
There is an important difference between using functions instead of classes, in that is: The framework is unaware of functions, but can see classes.
Consider the following “widget” function:
Widget functionWidget({ Widget child}) { return Container(child: child); }
used this way:
functionWidget( child : functionWidget() )
And it’s class equivalent:
class ClassWidget extends StatelessWidget { final Widget child; const ClassWidget({Key key, this.child}) : super(key: key); @override Widget build(BuildContext context) { return Container( child: child, ); } }
Used like that:
new ClassWidget( child : new ClassWidget(), )
On paper, both seem to do the same thing: Create 2 Container, with one nested into the other. But the reality is slightly different.
In the case of functions, the generated widget tree looks like this:
Container Container
While with classes, the widget tree is:
ClassWidget Container ClassWidget Container
This is important because it changes how the framework behaves when updating a widget.
Why that matters
By using functions to split your widget tree into multiple widgets, you expose yourself to bugs and miss on some performance optimizations.
There is no guarantee that you will have bugs by using functions, but by using classes, you are guaranteed to not face these issues.
Here are a few interactive examples on Dartpad that you can run yourself to better understand the issues:
- https://dartpad.dev/1870e726d7e04699bc8f9d78ba71da35
This example showcases how by splitting your app into functions, you may accidentally break things like AnimatedSwitcher
- https://dartpad.dev/a869b21a2ebd2466b876a5997c9cf3f1
This example showcases how classes allow more granular rebuilds of the widget tree, improving performances
- https://dartpad.dev/06842ae9e4b82fad917acb88da108eee
This example showcases how, by using functions, you expose yourself to misusing BuildContext and facing bugs when using InheritedWidgets (such as Theme or providers)
Overall, it is considered a bad practice to use functions over classes for reusing widgets because of these reasons.
You can, but it may bite you in the future.
- Avoid rebuilding all the widgets repetitively
- And it would be a good idea to add const.
- User itemExtent in ListView for long lists.
Specifying an itemExtent is more efficient than letting the children determine their extent because the scrolling machinery can make use of the foreknowledge of the children’s extent to save work, for example when the scroll position changes drastically.
- Avoid rebuilding unnecessary widgets inside animatedBuilder
body: AnimatedBuilder( animation: _controller, builder: (_, child) => Transform( alignment: Alignment.center, transform: Matrix4.identity() ..setEntry(3, 2, 0.001) ..rotateY(360 * _controller.value * (pi / 180.0)), child: CounterWidget( counter: counter, ), ), ),
This will rebuild the CounterWidget widget whenever animation occurs. If you put log in to counterWidget’s build method then you can see it will print a log every time whenever animation occurs.
Use it like this.body: AnimatedBuilder( animation: _controller, child : CounterWidget( counter: counter, ), builder: (_, child) => Transform( alignment: Alignment.center, transform: Matrix4.identity() ..setEntry(3, 2, 0.001) ..rotateY(360 * _controller.value * (pi / 180.0)), child: child ), ),
We use the child attribute provided by the AnimatedBuilder, which allows us to cache widgets to reuse them in our animation. We do this because that widget is not going to change, the only thing it will do is rotate, but for that, we have the Transform widget.
see for more information :2. Use const where possible
class CustomWidget extends StatelessWidget { const CustomWidget(); @override Widget build(BuildContext context) { ... } }
- When building your widgets, or using flutter widgets. this helps flutter to rebuild only widgets that should be updated. The widget will not change when setState calls. It will prevent the widget to rebuild.
- You can save CPU cycles and use them with a const constructor.
3. Use nil instead const Container()
// good text != null ? Text(text) : const Container() // Better text != null ? Text(text) : const SizedBox() // BEST text != null ? Text(text) : nil or if (text != null) Text(text)
A simple widget to add in the widget tree when you want to show nothing, with minimal impact on performance.
4. Accelerate Flutter performance with Keys
// FROM return value ? const SizedBox() : const Placeholder(), // TO return value ? const SizedBox(key: ValueKey('SizedBox')) : const Placeholder(key: ValueKey('Placeholder')), ---------------------------------------------- // FROM final inner = SizedBox(); return value ? SizedBox(child: inner) : inner, // TO final global = GlobalKey(); final inner = SizedBox(key: global); return value ? SizedBox(child: inner) : inner,
By using keys flutter recognizes widgets better. This gives better performance.
5. Optimize memory when using image ListView
ListView.builder( ... addAutomaticKeepAlives: false (true by default) addRepaintBoundaries: false (true by default) );
ListView couldn’t kill its the children are not being visible on the screen. It causes consumes a lot of memory if children have high-resolution images.
Doing these options false, could lead to the use of more GPU and CPU work, but it could solve our memory issue and you will get a very performant view without noticeable issues.
6. Follow Dart style
Identifiers: Identifiers come in three flavors in Dart
- UpperCamelCase names capitalize the first letter of each word, including the first.
- lowerCamelCase names capitalize the first letter of each word, except the first which is always lowercase, even if it’s an acronym.
- lowercase_with_underscores names use only lowercase letters, even for acronyms, and separate words with _.
DO name types using UpperCamelCase.
Classes, enum types, typedefs, and type parameters should capitalize the first letter of each word (including the first word), and use no separators.
good class SliderMenu { ... } class HttpRequest { ... } typedef Predicate<T> = bool Function(T value); const foo = Foo(); @foo class C { ... }
DO name libraries, packages, directories, and source files using
For more styles check out dart styles:For more rules checkout dart linter:Good library peg_parser.source_scanner; import 'file_system.dart'; import 'slider_menu.dart'; Bad library pegparser.SourceScanner; import 'file-system.dart'; import 'SliderMenu.dart';
7. Use MediaQuery/LayoutBuilder only when needed
8. Make use of async/await instead of then function.
9. Efficiently use operators
var car = van == null ? bus : audi; // Old pattern var car = audi ?? bus; // New pattern var car = van == null ? null : audi.bus; // Old pattern var car = audi?.bus; // New pattern (item as Car).name = 'Mustang'; // Old pattern if (item is Car) item.name = 'Mustang'; // New pattern
10. Make use of interpolation techniques
// Inappropriate var discountText = 'Hello, ' + name + '! You have won a brand new ' + brand.name + 'voucher! Please enter your email to redeem. The offer expires within ' + timeRemaining.toString() ' minutes.'; // Appropriate var discountText = 'Hello, $name! You have won a brand new ${brand.name} voucher! Please enter your email to redeem. The offer expires within ${timeRemaining} minutes.';
11. Use for/while instead of foreach/map
You can check the comparison of loops in this article12. Precache your images and icons
precacheImage(AssetImage(imagePath), context); For SVGs you need flutter_svg package. precachePicture( ExactAssetPicture( SvgPicture.svgStringDecoderBuilder,iconPath),context, );
13. Use SKSL Warmup
flutter run --profile --cache-sksl --purge-persistent-cache flutter build apk --cache-sksl --purge-persistent-cache
If an app has janky animations during the first run, and later becomes smooth for the same animation, then it’s very likely due to shader compilation jank.
14. Consider using the RepaintBoundary widget
Flutter widgets are associated to RenderObjects. A RenderObject has a method called paint which is used to perform painting. However, the paint method can be invoked even if the associated widget instances do not change. That’s because Flutter may perform repaint to other RenderObjects in the same Layer if one of them is marked as dirty. When a RenderObject needs to be repainted via RenderObject.markNeedsPaint, it tells its nearest ancestor to repaint. The ancestor does the same thing to its ancestor, possibly until the root RenderObject. When a RenderObject’s paint method is triggered, all of its descendant RenderObjects in the same layer will be repainted.
In some cases, when a RenderObject needs to be repainted, the other RenderObjects in the same layer do not need to be repainted because their rendered contents remain unchanged. In other words, it would be better if we could only repaint certain RenderObjects. Using RepaintBoundary can be very useful to limit the propagation of markNeedsPaint up the render tree and paintChild down the render tree. RepaintBoundary can decouple the ancestor render objects from the descendant render objects. Therefore, it’s possible to repaint only the subtree whose content changes. The use of RepaintBoundary may significantly improve the application performance, especially if the subtree that doesn’t need to be repainted requires extensive work for repainting.
15. Use builder named constructors if possible
For example
Listview → Listview.builder
16. Proper disposal of data
Unnecessary RAM usage kills inside the app silently. So don’t forget to dispose your data
Some packages give auto-dispose support to their classes like Riverpod, GetX, get_it, flutter_hooks, etc.
If you don’t know what is Flutter hooks and how to use it16. Set cacheHeight and cacheWidth values to images
- You can reduce memory usage in this way
17. Don’t use referencing for your List and Map
Don’t use it likeList a = [1,2,3,4]; List b; b = a; a.remove(1); print(a); // [2,3,4] print(b); // [2,3,4]
Due to this whenever you tried to call any method for list A it will be automatically called for list B.
like a.remove(some); will also remove the item from the list b;
Use it likeList a = [1,2,3,4]; List b; b = jsonDecode(jsonEncode(a)); a.remove(1); print(a); // [2,3,4] print(b); // [1,2,3,4]
I hope this gives you some insights to improve your flutter app performance. Happy coding!