Flutter : Improving performance using widget key : Is it a myth?

Mario Gunawan
Flutter Tips
Published in
5 min readSep 15, 2023

--

Performance improvement in flutter comes in many forms. Some of it are using a column instead of a listview if possible, or separating widgets into stateful or stateless one. One trick that I was interested in after reading some articles is about reusing widget keys while switching widgets. I was pretty convinced by the logic behind it so I decided to experiment about it.

To understand how widget keys might help performance in Flutter, we first need to understand the basics of widget rendering.

Widget Rendering

A flutter widget is split into 3 parts:

  • The widget, which holds properties and declares callbacks through an API
  • The element, which holds the UI hierarchy
  • The Render Object, which does everything related to rendering a widget and receives input event

On a widget’s first render, flutter creates the widget itself, then creates an element for that widget and links it to the widget, and then creates a render object then links it to the element, and only then does the rendering start.

Now, let’s suppose there is a Text and a Button widget. The button widget will update the text’s color to red/blue when you click it.

Text(
"Hello world",
style: TextStyle(
color: textColor,
),
),
ElevatedButton(
child: const Text("Click me"),
onPressed: () {
setState(() {
if (textColor == Colors.green) {
textColor = Colors.red;
} else {
textColor = Colors.green;
}
});
},
)

When you open the widget inspector tool, and see the widget tree, the render object ID stays the same after clicking the button.

The id is #16afa

This is because the element and the Render Object do not get recreated during this whole process of re-rendering. Flutter only needs to update the Render Object, not create a new object.

For a complete explanation, watch the google developer talks in China, 2019. I promise you it’s worth it!

Widget Key

So, in theory, updating a widget is cheaper than creating a new one and that is the reason why Flutter is so adamant about reusing the widget thing. After reading some articles and after some experimenting, here are two rules in which Flutter will or will not reuse a widget

  • Render Object will not be recreated if the widget parents are exactly the same as before
  • Render Object will be recreated if it becomes a different widget or if its parent has changed

For example, if we have two rendering functions:

get firstWidget => Text(
"hello world",
);

get secondWidget => Text(
"xxx",
);

If we create a button that switches between the first and second widgets, the Text object reference will stay the same because Flutter will recognize that the two widgets have the same runtime type and will reuse the existing element and render the object.

But when we switch the widget up a little:

get firstWidget => Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
"xxx",
),
);

get secondWidget => Text(
"xxx",
);

The reference of the Text render object is not the same anymore. This is because Flutter cannot reuse the same widget if the widget tree at/above the widget have changed. This means that we will re-render the text from scratch even though we could definitely reuse the text object.

One way to force Flutter to re-use the same object is by assigning a key to the Text object.

final key = GlobalKey();

get firstWidget => Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
"xxx",
key: key,
),
);

get secondWidget => Text(
"xxx",
key: key,
);

After assigning a key, Flutter will re-use the Text widget when we switch from the first widget to the second widget. This, in theory, will increase performance because we are re-using the same widget.

The Question: Does Widget Key Improve Performance?

For the test, let’s consider three categories of widgets:

  • Small Widget Rebuild (Only Text widget)
  • Intermediate Widget Rebuild (4–5 widgets deep)
  • Huge Widget Rebuild (~10 widgets deep)

The performance will be measured by how many seconds it takes for a widget to completely rebuild. Each widget category will be tested a total of 20 times (10 times for no key assigned, 10 times with key assigned).

As for how we’re measuring the speed, we’ll be calculating when the widget finishes rendering (using addPostFrameCallback) and reducing it by when the widget starts rendering (the start of the build function).

Here are the results:

  • Small Widget — 25.2 ms (without key) vs 25.7 ms (with key)
  • Intermediate Widget — 10.9 ms (without key) vs 14.6 (with key)
  • Large Widget — 20 ms (without key) vs 19.8 (with key)

Since I was doubtful about the result (why small is > intermediate?), I ran the test again with a listview, a widget that’s famous for being pretty heavy, and here’s the result:

  • Listview Widget — 33.4 ms (without key) vs 51.3 ms (with key)

So, does adding a widget key (in this case a GlobalKey) to a widget ensure a faster rebuild performance? Based on the data above, no. But can it improve rebuild performance? Theoretically yes, so if you find anything that’s wrong in my approach, please do contact me.

Conclusion

Adding GlobalKey might not improve your app performance. It does help Flutter to know which render object to reuse, but the speed itself is not a guarantee.

If you find anything wrong with my approach, please do comment. I’ll be happy to be proven wrong since this is an interesting topic to me.

Some Interesting Things I Found

  • The widget rebuilds get significantly faster after the first rebuild. So, if you have a widget that re-renders a lot, the first rebuild will be the most crucial to optimize.

My Reading Material

  1. https://medium.com/flutter-community/elements-keys-and-flutters-performance-3ef15c90f607
  2. https://medium.com/flutter/keys-what-are-they-good-for-13cb51742e7d
  3. https://docs.flutter.dev/perf/ui-performance
  4. https://www.kodeco.com/30099270-debugging-layout-issues-using-the-widget-inspector

Technical Notes

  1. Instead of using the dev tool performance monitoring, I measured the time needed to rerender a widget by using the WidgetsBinding.addPostFrameCallback(…) to get the end time and set the start time on the build method.
  2. The test is run on Android Emulator Pixel 2

Repository: https://github.com/margunwa123/widget_key_performance_flutter

--

--

Mario Gunawan
Flutter Tips

I'm a passionate mobile / web developer. Writing articles about software development, mainly about flutter and react.