Keys! What are they good for?

Ahem, excuse me.

As you may know, under the covers, for every widget, Flutter builds a corresponding Element.

Just like constructing a Widget tree, Flutter also builds an Element tree.

The ElementTree is extremely simple, only holding information about the type of each widget and a reference to children elements.

You can think of think of the ElementTree like a skeleton of your Flutter app.

It shows the structure of your app, but all the additional information can be looked up via reference to the original widget.

The Row widget in the example above essentially holds a set of ordered slots for each of its children.

When we swap the order of the Tile widgets in the Row, Flutter walks the ElementTree to see if the skeletal structure is the same.

It starts with the RowElement, and then moves to its children.

The ElementTree checks that the new widget is the same type and key as the old one, and if so, it updates its reference to the new widget.

In the stateless version, the widgets don’t have keys, so Flutter just checks the type.

(If this seems like a lot of information at once, watch the animated diagram above.

)The underlying Element tree structure for stateful widgets looks a little different.

There are widgets and elements like before, but also there are a pair of state objects with them, and the color information is being stored there, not in the widgets themselves.

In the stateful Tile case without keys, when I swap the order of the two widgets, Flutter walks the ElementTree, checks the type of the RowWidget, and updates the reference.

Then TileElement checks that the corresponding widget is the same type (TileWidget) and it is, so it updates the reference.

The same thing happens for the second child.

Because Flutter uses the ElementTree and its corresponding state to determine what to actually display on your device, from our perspective, it looks like your widgets didn’t properly swap!In the fixed version with the stateful Tiles, I added key properties to the widgets.

Now if we swap the widgets The Row widgets match like before, but the key of the Tile Element doesn’t match the key of the corresponding Tile Widget.

This causes Flutter to deactivate those elements and remove the references to the Tile Elements in the Element Tree, starting with the first one that doesn’t match.

Then Flutter looks through to non-matched children of the Row for an element with the correct corresponding key.

It finds a match, and updates its reference to the corresponding widget.

Flutter then does the same thing for the second child.

Now Flutter will display what we expect, with the widgets swapping places and updating their color when I press the button.

So in summary, keys are useful if you’re modifying the order or number of stateful widgets in a collection.

For the sake of illustration, I stored the color as state in this example.

Often though, state is much more subtle.

Playing animations, displaying data that the user has entered, and scroll location all involve state.

Where do I put ‘em?Short answer: if you need to add keys to your app, you should add them at the top of the widget subtree with the state you need to preserve.

A common mistake I’ve seen is people thinking they only need to put a key on the first stateful widget, but there be dragons.

Don’t believe me?.To show what sort of trouble we can get into, I wrapped my colorfulTile widgets with padding widgets, but I left the keys on the tiles.

Now when I click the button the Tiles change to completely different random colors!Here’s what the WidgetTree and ElementTree look like with the padding widgets added:When we swap the positions of the children, Flutter’s element-to-widget-matching algorithm looks at one level in the tree at a time.

The diagram greys out the children’s children in the diagram so we can focus on one level at a time.

At that first level of children with the Padding elements, everything matches up correctly.

At the second level, Flutter notices that the key of the Tile Element doesn’t match the key of the widget, so it deactivates that Tile Element, dropping those connections.

The keys we’re using in this example are LocalKeys.

That means that when matching up widget to elements, Flutter only looks for key matches within a particular level in the tree.

Since it can’t find a tile element at that level with that key value, it creates a new one, and initializes a new state, in this case, making the widget orange!If we add keys at the level of the padding widgets:Flutter notices the problem and updates the connections correctly, just like it did in our previous example.

Order is restored in the universe.

What kind of Key should I use?The fine purveyors of Flutter APIs have given us a variety of Key classes to choose from.

The type of key you should use depends on what the distinguishing characteristic is for the items needing keys.

Take a look at the information that you’re storing in those widgets.

Consider the following To-do list app¹, where you can reorder the items in your TODO list based on priority and then remove them when you’re done.

In this scenario, you might expect the text of a To-do item to be constant and unique.

If that is the case, it is probably a good candidate for a ValueKey, where the text is the “value”.

return TodoItem( key: ValueKey(todo.

task), todo: todo, onDismissed: (direction) => _removeTodo(context, todo),);In a different scenario, perhaps you had an address book app that listed information about each user.

In this case each child widget stores a more complex combination of data.

Any of the individual fields, like a first name or birthday might be the same as another entry, but the combination is unique.

In this scenario, an ObjectKey is probably most appropriate.

If you have multiple widgets in your collection with the same value or if you want to really ensure each widget is distinct from all others, you can use the UniqueKey.

I used a UniqueKey in the example color-switching app because we didn’t have any other constant data that we’re storing in our tiles, and we don’t know what the color will be when we construct the widget.

However, one thing you don’t want to use is a random number for your key.

Every time a widget gets built, a new random number will be generated and you’ll lose consistency between frames.

Then you might as well not have used keys in the first place!PageStorageKeys are specialized keys that store a user’s scroll location so that the app can preserve it for later.

GlobalKeys have two uses: they allow widgets to change parents anywhere in your app without losing state, or they can be used to access information about another widget in a completely different part of the widget tree.

An example of the first scenario might if you wanted to show the same widget on two different screens, but holding all the same state, you’d want to use a GlobalKey.

In the second scenario, maybe you want to validate a password, but don’t want to share that state information with other widgets in the tree.

GlobalKeys can also be useful for testing, by using a key to access a particular widget and query information about its state.

Often (but not always!), GlobalKeys are a little like global variables.

There is usually a better way to look up that state, using InheritedWidgets, or something like Redux or the BLoC pattern.

Quick RecapIn summary, use Keys when you want to preserve state across widget trees.

This most commonly occurs when you’re modifying a collection of widgets of the same type, like in a list.

Put the key at the top of the widget tree you want preserved, and choose the key type you use based on the data you are storing in the widget.

Congratulations, you are now well on your way to becoming a Flutter Sorcerer!.Oh, did I say sorcerer?.I meant sourcerer, as in someone who writes app source code…which is almost just as good.


⚡[1] Code for the To-do app inspired by https://github.


. More details

Leave a Reply