Skip to main content

Command Palette

Search for a command to run...

Flutter Performance Tip: Mind your build

Improve your Flutter app's performance by using the build method correctly.

Published
4 min read
Flutter Performance Tip: Mind your build
G

A developer from sunny South Africa. I have a YouTube channel, Fun with Flutter, that's all about Flutter development.

We're developers. We like fast cars and fast Flutter.

Welcome to the first of many Flutter performance tips.

This article also has a companion video that gives the exact same information, but with music and visuals.

First, an overview of widgets!

Immutable-UI-Blueprints.gif

Widgets are immutable UI blueprints for RenderObjects. They're meant to be recreated a lot. For Flutter to remain fast your widgets need to be created fast.

Lets explore an example. Keep in mind it's just an example to illustrate a point.

...

/// Not really long, just a demo.
const superLongListOfNames = ['Gordon', 'George', 'Hannah', 'Harry', 'Dan'];

/// Expensive operation, just a demo.
List<String> namesThatStartWith(String l) {
  print('expensive function $l');
  return superLongListOfNames
      .where((element) => element.startsWith(l))
      .toList();
}

class OhNoWidget extends StatelessWidget {
  const OhNoWidget({
    Key? key,
    required this.letter,
  }) : super(key: key);

  final String letter;

  @override
  Widget build(BuildContext context) {
    print('building');
    List<String> filteredNames = namesThatStartWith(letter); // This is the ouchie

    return ListView.builder(
      itemCount: filteredNames.length,
      itemBuilder: (context, index) {
        return Padding(
          padding: const EdgeInsets.all(32.0),
          child: _Tile(title: filteredNames[index]),
        );
      },
    );
  }
}

...

Above you have a StatelessWidget; this widget takes in a String property, called letter.

You use this property to filter a long list of names that start with that letter. You have a function called namesThatStartWith and you filter the list superLongListOfNames.

You call this function in the build method, and then you use the result to display the names that start with that letter.

There are two problems with this widget.

First, if you assume this list is really long, then you should probably do this operation in a separate isolate instead. We'll talk about isolates in a future article.

Seconds, you're calling namesThatStartWith from the widget's build method; this is the main issue that we're focussing on in this article.

This means this operation will be performed EVERY time the build method is called. Meaning any time a parent is updated, or new (unrelated) state is passed in, or the user navigates the app. The build method is SUPPOSED to be called a lot, and that is part of how Flutter is designed. Also, keep in mind that you don't always have control over when a build is triggered.

Here is an example of what this output would look like:

demo_screen.jpg

There's an extra button to trigger a call to setState higher up in the widget tree.

When that button is tapped the build method will be called again and as a side effect the expensive function will be called as well.

Keep your build method pure and free of side effects!

In this example you can fix the issue by making this a StatefulWidget, and moving the call to the expensive function to initState.

class OhNoWidget extends StatefulWidget {
  const OhNoWidget({
    Key? key,
    required this.letter,
  }) : super(key: key);

  final String letter;

  @override
  _OhNoWidgetState createState() => _OhNoWidgetState();
}

class _OhNoWidgetState extends State<OhNoWidget> {
  late List<String> filteredNames;

  @override
  void initState() {
    super.initState();
    filteredNames = namesThatStartWith(widget.letter); // Moved here
  }

  @override
  Widget build(BuildContext context) {
    print('building');

    return ListView.builder(
      itemCount: filteredNames.length,
      itemBuilder: (context, index) {
        return Padding(
          padding: const EdgeInsets.all(32.0),
          child: _Tile(title: filteredNames[index]),
        );
      },
    );
  }
}

And there you go 👍🏼. Point is to keep your builds clean and don't do expensive work directly in the build. Extract that logic to whatever form of state management you use.

This logic would look different depending on how you manage your state and trigger rebuilds in your app.

For this particular example when using setState, take note that if the letter property you pass in were to change, then you'd need to filter the list again.

You can do that by overriding didUpdateWidget.

Just add a check to see if the oldWidget.letter value and the new widget.letter value are different. If yes, recompute and set the local filteredNames state.

@override
  void didUpdateWidget(OhNoWidget oldWidget) {
    super.didUpdateWidget(oldWidget);

    if (oldWidget.letter != widget.letter) {
      filteredNames = namesThatStartWith(widget.letter);
    }
  }

That's that!

Ask question down below, and let me know ideas for future performance tips. Until then, code fast, Flutter faster. Cheers.

S

_Tile does not be shown in the video or your article that causes error

S

It's very useful to apply to my own project.

G

Thanks for this article.

I'll surely keep this in mind :)

Flutter Performance Tips

Part 1 of 1

A series discussing Flutter performance tips and tricks. Everything you will need to know to keep your widgets running faster that you can say F1.