Tired of academic articles? Today, let’s enjoy a detective-style article for a change of pace.
Case Introduction
Key appears everywhere in Flutter, from StatefulWidget to StatelessWidget. It seems so close yet so far, familiar yet mysterious. Although Flutter developers often work with Widget, Key—the silent player behind all power transfers—rarely gets attention.
Today, the Flutter Vietnam detective team will take you deep into the corners of the Flutter gang, decoding the role of Key in optimizing performance for Flutter apps, and exploring best practices for using them.
Searching for the Mastermind
The definition of Key in the documentation states:
A Key is an identifier for Widgets, Elements and SemanticsNodes.
A new widget will only be used to update an existing element if its key is the same as the key of the current widget associated with the element.
Translation:
Key is an identifier for Widget, Element, and SemanticsNodes.
A new widget will only be used to update an existing element if its key matches the key of the current widget associated with that element.
As you know, in Flutter, everything is a Widget. This Widget gang includes familiar faces like Row, Column, Container… But these Widgets are just henchmen; according to our intelligence, the gang is run by a notorious boss: Element.
He manipulates all Widgets, from calling initState
, build
, dispose
of Widgets to managing the Widget Tree. Element is also a crucial link between Widget and RenderObject—the one that draws the UI on the screen—to create masterpieces.
However, today we’re not taking down the boss or the whole gang, just remember that Element orchestrates everything. The fish isn’t big enough yet to cast the net. The goal of this case is to investigate Key and the four masters.
Key
When you rebuild a Flutter app, do you know what happens in the shadows? Element decides which Widgets to keep, replace, or remove. This is where Key starts to show its power.
In the Widget world, whenever changes occur, Widgets aren’t just updated but are destroyed and reborn. Key is the identity card that helps Widgets retain their identity during rebirth. After the Widget tree is rebuilt, Element uses Widget type and Key to decide whether the Element should be rebuilt. If Widget types differ, the Element is destroyed and recreated. Rebuilding the boss is much more expensive than rebuilding the henchmen Widgets. This can cause unwanted performance issues and sometimes make your app lag.
If Widget types are the same, Element compares the Key. If the Key matches, Element only updates the widget. Otherwise, Element is deactivated, meaning it’s temporarily removed from the Element tree and may be reattached later.
There are two main types of Key: LocalKey and GlobalKey. LocalKey is further divided into UniqueKey, ValueKey, and ObjectKey. Let’s unmask each one.
UniqueKey – The Untraceable Assassin
A mysterious figure, appearing and disappearing, never showing up twice with the same value. It creates unique values to help Flutter distinguish between two Widgets even if they have the same type. Use it when you don’t want to reuse any Widget, ensuring the Widget is completely rebuilt.
First, let’s create an Item widget that simply displays a Text, but we’ll use StatefulWidget
so you can clearly see the init and rebuild process.
|
|
Next, create a ListView
containing those Item widgets, without passing any Key to the items.
|
|
Check the log: when you click the FloatingActionButton
to create each item, only the newest widget calls initState
, others just rebuild
. Here’s the log when creating the 5th item:
|
|
Now, let’s assign the UniqueKey assassin. Add UniqueKey to Item:
|
|
Everything changes: when clicking FloatingActionButton
to create each item, all widgets are recreated. Even the Key value changes each time. Here’s the log for the 5th item; not only item 5 but all items 1 to 4 are also re-initialized:
|
|
ValueKey – The Reliable Butcher
A simple-minded but highly effective worker, ValueKey is perfect when you have a clear identifier value, such as a String
or int
. This butcher helps Element know which Widget to keep based solely on that identifier, allowing you to reuse Widgets when the Key value doesn’t change.
Let’s update the previous ListView
example to ReorderableListView
. Since ReorderableListView
requires each item to have a Key, we’ll start with Item using ValueKey
.
|
|
Now, whenever you drag to reorder items, you’ll see the items being rebuilt:
|
|
What if you change the value of an item after dragging by updating the onReorder
function? For example, when dragging ‘Henry’, change its value to ‘Henry Changed’:
|
|
The value changes, so the ValueKey changes, and the item is recreated:
|
|
ObjectKey – The Strategic Advisor
Unlike ValueKey, this one is a master strategist, holding a complex object. ObjectKey relies on object reference. Two Keys are considered equal only if they reference the same object.
Similar to the ValueKey example, let’s update it to use ObjectKey instead.
|
|
GlobalKey – The Powerful Butler
In the gang, GlobalKey is the strongest. It knows everything in the Widget Tree. Not only does it store identity, but it also manages the entire state and allows direct access to State
. This brings flexibility but can be easily abused.
The most common example is using GlobalKey to control and validate the state of a Form
.
|
|
Best Practices for Using Key
Key is not just a tool, but a treasure for controlling your app. Understanding and using Key correctly not only optimizes performance but also ensures your app’s logic remains stable and accurate. Overusing it can make your code unnecessarily complex, so use it only when needed. Remember, in the ever-changing world of Flutter, Key is the key to smooth apps you develop!