Blog Details

image
image
image
image
image
image
image
image
image

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 this
Widget _buildFooterWidget() {
  return Padding(
    padding: const EdgeInsets.all(8.0),
    child: Text('This is the footer '),
  );
}
Use it like this
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:

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
Don’t use it like this
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

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';
For more styles check out dart styles:For more rules checkout dart linter:

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 articleimage

12. Precache your images and icons

precacheImage(AssetImage(imagePath), context);
For SVGs
you need flutter_svg package.

precachePicture(
  ExactAssetPicture(
    SvgPicture.svgStringDecoderBuilder,iconPath),context,
);
image

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 itimage

16. 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 like
List 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 like
List 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!

Happy Hunting!