- Keys uniquely identify elements in the widget tree
- Keys are necessary to enhance the performance of widget matching, which improves the efficiency of rebuilding the widget tree
- Keys are beneficial for reordering child widgets, such as in
- There are different types of keys including
GlobalKeys which should be used in different situations
Keys are an interesting topic in Flutter. Especially when starting to learn it, you can easily get around understanding and even using it.
Most often, you’ll have an unexplainable bug in your application when you realize the root cause is not having used keys.
This is when you know it’s about time to get a better understanding of it.
What are keys in Flutter?
The main purpose of keys are to maintain a widget’s state even if it’s moved or duplicated inside the widget tree.
It’s a core topic to understand as a Flutter developer because there are several situations in which keys are necessary to maintain the robustness of the application.
Not using keys at all can lead to undesired behavior that shows subtly during the usage of your apps. In order to prevent these unwanted UX glitches, it’s good to know the meaning of keys.
To sum it up, in Flutter, a key is a unique identifier for a widget. It aids the framework in tracking widgets in the widget tree.
Why is this necessary?
When you think about it, you might come to the bottom line question: if it’s so hard for beginners to understand and often leads to errors, why did they make it like that? Why is it necessary to provide keys? Couldn’t the framework handle this on its own?
To answer it in one word: performance.
This has to do with the way, Flutter decides whether it should re-render a widget or not. This is crucial from a performance perspective because re-rendering is a very expensive operation in terms of CPU time.
Element tree and widget tree
In order to dive a little bit deeper into the topic of re-rendering widgets, let’s talk about the element tree and the widget tree.
Like I have already mentioned in my article about
BuildContext, you need to understand the fundamental difference between widgets and elements to fully grasp the underlying mechanics.
In Flutter, keys are necessary because they provide a way to make the element tree more efficient and improve performance.
Flutter builds a visual tree comprising of mutable copies of widgets, called
Elements. However, in the usual course of app development, developers don’t interact directly with
Elements as the framework handles them.
Each widget in Flutter has a corresponding
Element instance. When a widget rebuilds, the framework creates a new widget tree and a new element tree. The element tree is used to map the widget hierarchy to the rendering pipeline, which generates output for the app.
Without keys, the framework has to check each widget in the new tree against each widget in the old tree to determine whether they are the same, have moved, or have been removed.
However, with keys, the framework can identify which elements in the new tree correspond to the same elements in the old tree. This reduces the number of widget comparisons needed during the widget matching phase, which can lead to significant performance improvements.
In addition, keys have other benefits such as improving the efficiency of
ListView and other widgets that can re-order their child widgets. By assigning keys to individual list items, Flutter can more efficiently identify when items are added, removed, or updated, and animate those changes more smoothly.
In summary, keys are necessary in Flutter for performance reasons. By providing a way to more efficiently update the element tree, Flutter can reduce the amount of work the framework needs to do when rebuilding the widget tree, resulting in improved performance and a smoother user experience.
When to use Keys?
If you find yourself in any way altering (e. g. adding, removing, reordering) a list of widgets of the same type that hold state, chances are high you need any type of key.
Keys should be used with particular widgets such as
ListView and some stateful widgets that need to maintain their state across page navigations.
For example, consider a dog sitting app that displays a list of offers 🐶. We imagine these offers being part of a
So far so good. Every element in the
ListView, which we name
SittingOfferTile, has a representation in the widget tree and thus in the element tree.
Let’s say the list can be sorted by different criteria, such as payment or distance. In this case, a key can be used to ensure that each item maintains its state even when the list is sorted. Without keys, the item state would not be preserved, and it would be challenging to maintain the order and state of the list.
Let’s pretend the user has chosen a sort method which swapped the order of the first two offers and have a look at how things prevent themselves from Flutter’s perspective.
In the above image we can see the status quo: before the sorting, the first
SittingOfferTile in the widget tree (the offer for 12 €) is the first entry in the element tree. The second element behaves analogously.
Now comes the crucial part: after the sorting has taken effect, from Flutter’s perspective, nothing has changed which prevents it from re-rendering.
This is because Flutter doesn’t take the actual content of the widget into account but only looks at the runtime type.
First and second child of the
ListView have the runtime type
First and second child of the
ListView still have the runtime type
→ It seems like nothing has changed! No reason to re-render.
This is where keys come in handy: they provide a way for Flutter to keep track of a widget regardless of their type. By assigning a unique key, Flutter knows: “Ah, the widget at position 3 has they key ‘abc’. It must be the same widget that as been at position 1 in the last render because the widget as position 1 used to have the very same key (‘abc’).”
Key will only affect the direct cildren of the
Different types of keys
Okay great. We have figured out that keys are the solution to the problem of Flutter’s framework not being able to distinguish multiple widgets of the same type inside a list.
The question that comes up is: What keys should we give these widgets?
1, 2, 3? A, b, c? Or even something randomly generated?
This is where key types come into play.
There are a bunch of
Keys you can use. The central questions you should ask yourself when deciding which one to use are:
- What kind of data do I have to uniquely identify a widget?
- In which scope is uniqueness defined here?
We will discuss these two questions for every type of
Key with regard to our dog sitting app example.
Keys how I have explained them so far are only a concept. Since we are in an object-oriented language, each
Key corresponds to a class. This means, every type of
Key has a corresponding class.
Let’s start with the simplest one:
Key is the abstract class all other
Keys inherit from. That means you can’t instantiate it.
This means, if you try to instantiate it like this:
Key key = Key('…'), the runtime type of this object will be
This is how the class is defined:
As you can see, the
Key factory exclusively accepts a single
Going back to our example, let’s say the above mentioned
SittingOfferTiles are created from a model called
SittingOffer, which is defined as follows:
Now we could just use the
name property of the
SittingOffer class as it’s the only
String property of the class.
We could then utilize it like this in the
The issue we have here is that we assume uniqueness regarding the
name. If the same name appears more than once, we will have the same issue with these duplicated entries because Dart will treat them as equal and won’t notice them swapping.
Having used the
name property as the
Key seems like an arbitrary decision. Having to explicitly choose a certain
String property of the underlying model can be circumvented by using what is called an
Like the name implies, the
ObjectKey uses the whole object for comparison instead of only one of its values. In this case it would compare the
We could change our code change to make use of it:
Its only constructor argument is of type
Object? which means that it will work with every kind of object.
Using this kind of
Key is useful for situations where multiple widgets may have the same values but are actually separate instances.
ObjectKey uses instance comparison with the identity function to determine equality, implying that the same object in terms of memory address will be regarded as equal.
If you have a scenario in which the same object appears multiple times within a
ObjectKey might not be the best choice. As when re-ordering happens, Flutter will be unable to distinguish between those objects.
In this case, a
ValueKey could be a better choice. Instead of using the identity function, it utilizes the the == operator. Although it defaults to the same behavior as the identity function, it can be overridden.
So if you have a custom way of checking equality for your very own class then it will use this way of comparing.
It also plays nice with packages like equatable.
Let’s say instead of dog sitting offers, you have a much simpler list with elements that do not have an underlying model.
This can be for example a list of colors you have stored locally where each element previews the color and the same color can occur more than once.
There is no such thing as an id and you also can not use the
Color object as the
Key as it can be the same for duplicated colors.
In this case the
UniqueKey might be the best option.
UniqueKey creates a key that is equal only to itself. In comparison to other
Key types that have already been mentioned, this one does not offer a
const constructor as it would imply that all instantiated keys were the same instance and therefore not unique.
This key type is generated randomly and uniquely whenever a widget is created. It is particularly useful when creating a widget multiple times with different properties, where we can assign different keys.
It’s also the only
Key that doesn’t have a constructor argument.
We would use it like this:
For the sake of completeness:
LocalKey is a subclass of
Key and the superclass for all of the other
Keys we have looked at so far.
It’s used to identify widgets within the same parent widget. Local keys cannot be used to identify widgets outside of their parent.
LocalKey itself is abstract and there is no way to directly instantiate it.
This is a type of
Key that can be used to identify a widget from anywhere in the widget tree. Unlike
LocalKeys, which can only identify widgets within the same parent,
GlobalKey can identify widgets from anywhere in the widget tree.
It’s an abstract class but it offers a factory constructor for instantiation.
Concrete usage examples are:
- Moving widgets from one parent to another preserving it’s state
- Form validation
- Displaying the very same widget in multiple screens and holding its state
- Using an AnimatedList (adding or removing elements from outside the list)
There is one subclass of
GlobalKey which is called
LabeledGlobalKey. It’s not a completely new
Key type but instead used to give the
GlobalKey a label which can be used for debugging.
Using the only constructor argument of
GlobalKey also utilizes
LabeledGlobalKey and uses the argument given as the debug label.
LabeledGlobalKey is only a debug argument, it’s not used for comparing the identity. It’s purely a convenience concept for the developers.
In conclusion, keys are a crucial component of Flutter applications, and they must be used appropriately to ensure that widgets maintain their state and can be efficiently updated or removed from the widget tree.
Keys are used to ensure that the framework can identify and track widgets, even if they are moved or duplicated in the tree.
When working with dynamic widgets, animations, or duplicate widgets, keys must be used to maintain the state of the widget and ensure that the app works as expected.
In most situations, you will have a list of items that have an underlying model most often coming from an API or some kind of data source. In this case, you can just use an
ObjectKey in which you put this model.
If there is no such thing as an identifier and the list is generated “on the fly”, then a
UniqueKey is most often the best solution.
Lastly, when you have a situation, in which you need to access items from all over the app or at least not only from the parent widget, then a
GlobalKey can be useful. This can be the case for form validation or