Keys In Flutter - UniqueKey, ValueKey, ObjectKey, PageStorageKey, GlobalKey
All you need to know about Keys in Flutter

I'm a mobile/web developer ๐จโ๐ป who loves to build projects and share valuable tips for programmers
Follow me for Flutter, React/Next.js, and other awesome tech-related stuff ๐
Introduction
- Flutter has many widgets in its library, and if you take a look at every widget's properties you'll most probably find the
keyparameter in all of those widgets. - Most of the beginners who started learning Flutter don't know about
Keys. And if they do, they don't know how to use them or where to put them. - It's because
Keyshas very few use cases or you can say their use is less common. - In this blog, I will try to explain the concept of
Keys, and different types ofKeys, Where to useKeys, and How to use them. 
What is Key??
- If we take a look at the definition of
Keyswritten in Flutter's Official Documentation, It says :A Key is an identifier for Widgets, Elements, and SemanticsNodes.
- It simply means is Flutter basically identifies the widgets and where it is placed in widget tree by
Keys. But it's more than that. Keyspreserves thestatewhen you move around the widget tree.If you find yourself adding, removing, or reordering a collection of widgets of the same type that hold some state, using keys is likely in your future.
- Let's take a look at different types of keys one by one.
UniqueKey :
- The
UniqueKeyin Flutter is used to identify every widget of your app uniquely. UniqueKeyalso preserves the state when widgets move around in your widget tree.UniqueKeycan be used in cases like when you are reordering the widget in the list or adding or removing the widgets from a list.- It is helpful when you have multiple widgets in your widget tree with the same values and same type and you want to identify each of them uniquely.
- It is also helpful when a unique id is not defined in your DB collection to identify all the list of items uniquely. You can use
UniqueKeywhich will assign a unique key to that particular widget. - Let's take one example
- I'm going to create an emoji swapper program, where there are going to be two emojis displayed on the screen, and a button underneath them, which will swap the position of the emojis when triggered.
class HomePage extends StatefulWidget { @override _HomePageState createState() => _HomePageState(); }class _HomePageState extends State<HomePage> { List<Widget> emojis = [ GetEmoji(emoji: "๐"), GetEmoji(emoji: "๐ค ") ]; swapEmoji() { setState(() { emojis.insert(1, emojis.removeAt(0)); }); } @override Widget build(BuildContext context) { return Scaffold( body: SizedBox.expand( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: emojis, ), SizedBox( height: 20, ), ElevatedButton( onPressed: swapEmoji, child: Text("Swap"), ) ], ), )); } }class GetEmoji extends StatelessWidget { GetEmoji({required this.emoji}); String emoji; @override Widget build(BuildContext context) { return Text( emoji, style: TextStyle( fontSize: 100, ), ); } }- And as you can expect, the emojis will swap their position when we click the swap button

But the problem will come if we try to convert the
Statelesswidget intoStatefulwidget and store the value in astate.- Let's see what will happen if we convert it.
class GetEmoji extends StatefulWidget { GetEmoji({required this.emg}); String emg; @override _GetEmojiState createState() => _GetEmojiState(); }class _GetEmojiState extends State<GetEmoji> { late String emoji; @override void initState() { super.initState(); emoji = widget.emg; } @override Widget build(BuildContext context) { return Text( emoji, style: TextStyle( fontSize: 100, ), ); } }
- Now nothing is happening. The emojis are no longer changing their positions.

- It's because under the hood Flutter distinguishes widget by the
typeof the widget (runtimeType) and by thekeys. - We've two
Statefulwidgets in our list (one for ๐, and one for ๐ค ). when we swap the emojis positions, by pressing theswapbutton, flutter will then check in theElementTreethat, Is thetypeof the changed widget is the same as thetypeof theElementTree's element or not?. - If you don't know about
ElementTree, TheElementTreeonly holds the information about the type of each widget and a reference to children's elements. You can think of the ElementTree as a skeleton of your Flutter app. It shows the structure of your app.

- After swapping it will check the runtime type.

- When I've swapped the order of the two widgets, Flutter walks the
ElementTree, checks the type of theRowWidget, and updates the reference. After that, it checks if the type of๐ Text Elementof the ElementTree is same as๐ค Text Widget'stype? and it is, so it updates the reference. And nothing will update. - But..but If we name those two
Statefulwidgets differently. Then there will be no problem. Because both will then have different IDs/keys assigned. - To update the widget which has the same type inside the list, we have to pass the
UniqueKeyto all the widget class GetEmoji extends StatefulWidget { GetEmoji({required this.emg, required Key key}) : super(key: key); String emg; @override _GetEmojiState createState() => _GetEmojiState(); }List<Widget> emojis = [ GetEmoji( emg: "๐", key: UniqueKey(), ), GetEmoji( emg: "๐ค ", key: UniqueKey(), ), ];
- Here what is happening is when flutter tries to match the
type, its gets matched. But when it is trying to match keys it will not match. And in the element tree, askeysare not matching, it will change the references and update the app. Swapping widget

Keys not matched

- Change references

- Swap of Elements in ElementTree

Where to put Keys
- NOTE one thing, That if you need to add keys to your app, you should add them at the
topof the widget subtree. Otherwise, you'll get some weird results. - Let me explain with an example.
- Here I've used the previous example only. Just added a background color which is generated randomly.
- Everything is running.

- Now let's wrap our
GetEmojiwidget withContainerwidget. Now observe hereUniqueKeyis not at thetopof its widget tree. List<Widget> emojis = [ Container( child: GetEmoji( emg: "๐", key: UniqueKey(), ), ), Container( child: GetEmoji( emg: "๐ค ", key: UniqueKey(), ), ), ];- And now let's run the app again

- Here the Widgets are swapping, that's okay but the new color is generating again and again when the swap button is triggered.
- In fact, It is not only the new color that is generating again and again but also a new
Text Widgetis generating again and again in the widget tree, we are not able to see that because we're using two static emojis only. - We've already assigned the
Keysto the widget RIGHT? But it's not about the keys which are creating a problem it's about thepositionof the keys. - So what is going on?.
Here is the structure of the Widget and Element Tree

Here when we perform the swap operation, Flutterโs element-to-widget-matching algorithm looks at only one level in the tree 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
๐ Container Elementdoesnโt match the key of the widget, so it deactivates that๐ Container Element, dropping those connections. 
- The keys weโre using in this example are
LocalKeys. That means that when matching up widgets to elements, Flutter only looks for key matches within a particular level in the tree. - Since it canโt find a
Container Elementat that level with thatkeyvalue, it creates a new one, and initializes a new state, in this case, making the widget with the random background color. 
- This we can solve by adding the
keyin thePaddingwidget. - So moral of the story is YOU HAVE TO DEFINE THE KEY AT THE TOP OF THE WIDGET TREE.
ValueKey :
- A key that uses a
valueof a particular type toidentifyitself. - The
ValueKeyis useful if we want to preserve the state of the Stateful widgets when they move around the widget tree. - We can use the
ValueKeywhen we want to removeWidgetfrom the widget tree, or reordering the list. - Consider the below code, where there are 2
Textfieldwidget. And we want to remove the lastTextfieldfrom the widget tree. bool showFavouriteFramework= true; //... Column( mainAxisAlignment: MainAxisAlignment.center, children: [ if (showFavouriteFramework) TextField( decoration: InputDecoration( border: OutlineInputBorder(), labelText: "Favourite Framework"), ), TextField( decoration: InputDecoration( border: OutlineInputBorder(), labelText: "Favourite Language"), ), SizedBox(height: 10), ElevatedButton( onPressed: () { setState(() { showFavouriteFramework = false; }); }, child: Text("Remove Favourite Framework field"), ) ], ),
- When we press the
Remove Favourite Framework fieldbutton. 
- If you observe, we got the
Textof Favourite Framework's Textfield i.eFlutterin the Textfield of Favourite language's Textfield instead ofDart. - NOTE this will only happen if you use the multiple Stateful widgets of the same type.
- What's happening here is, when we're removing the first Textfield, Flutter is not able to identify which TextField it has to remove, because both are of the same type.
- We've to provide those widgets unique values that can help flutter to identify that they are different.
- We can provide Unique values with the help of
ValueKey. TextField( key: ValueKey("Framework"), decoration: InputDecoration( border: OutlineInputBorder(), labelText: "Favourite Framework" ), ), TextField( key: ValueKey("Language"), decoration: InputDecoration( border: OutlineInputBorder(), labelText: "Favourite Language" ), ),
- Now as we can see Favourite Language got its actual value as expected.
- Here Flutter will first check the type of those two widgets and check if it is the same or not, and it is. Then after it'll check if the
keysare of the same type or not, and it's not. So flutter will update the state and references accordingly. - Here in the
ValueKeyyou can provide any type of unique values, like,String,int,double,Objects, etc. But all the widgets must have unique values. That you should keep in mind. Otherwise, it'll not work.
One important thing is when we have a list of widgets inside
Listview,Column,Row, try to avoid giving theindexvalue coming from the list as thekey.
ObjectKey
- A key that uses a reference of a particular type to identify itself.
- The
ObjectKeyis useful if we want to preserve the state of the Stateful widgets when they move around the widget tree. ObjectKeycan be used in cases like when you are reordering the widget in the list or adding or removing the widgets from a list.- Let's take an example.
- I've created a List of SuperHero objects from the class SuperHero.
late List<SuperHero> superHeroList; @override void initState() { superHeroList = [ SuperHero(movie: "Iron Man", name: "Tony Stark"), SuperHero(movie: "Hulk", name: "Bruce Banner"), SuperHero(movie: "Thor:Ragnarok ", name: "Thor"), ]; super.initState(); }Scaffold( floatingActionButton: FloatingActionButton( onPressed: () { setState(() { superHeroList.insert(0, superHeroList.removeAt(1)); }); }, child: Icon(Icons.swap_calls), ), body: Center( child: Column( children: superHeroList .map<Widget>((hero) => HeroWidget(hero: hero)) .toList(), ), ), );- This program will swap the first two item's position in the
superHeroList. - But if we try to swap these two items, something is going wrong, here if you see the element's text is interchanging but the colors are not. It should also change, right? because the color is also connected with that list item.

- As we've discussed in the unique key, that flutter is not able to distinguish the widget because of the same type.
- Then what's the solution?
- You might think, we can use
ValueKey, Right?. And yes you're right we can useValueKeyto distinguish the list widgets. But there will be an issue. Let's see what happen if we consider theValueKeyin this situation. - Giving the
ValueKeytokeyparameter. Center( child: Column( children: superHeroList .map<Widget>( (hero) => HeroWidget( key: ValueKey(hero), hero: hero, ), ) .toList(), ), ),- And we'll get the result as expected. The items are swapping with color now.
-BUT...but, Now add the same Objectin the list.superHeroList = [ SuperHero(movie: "Iron Man", name: "Tony Stark"), SuperHero(movie: "Iron Man", name: "Tony Stark"), SuperHero(movie: "Hulk", name: "Bruce Banner"), SuperHero(movie: "Thor:Ragnarok ", name: "Thor"), ];- And the output is........


- Flutter will throw an error something like this :

- And it's correct because what we've seen in the
ValueKeyexplanation is that the `widget is identified by its value when we use ValueKey - Here, in this case, we've added two same objects with the same value. That's why Flutter is throwing an error that Hey, I found duplicate keys.
- In such cases, we have to use
ObjectKey. - As we've seen in the definition of the
ObjectKey, thatObjectKeywill distinguish the item based on the references. - So let's try to add
ObjectKeyin thekeyparameter. HeroWidget( key: ObjectKey(hero), hero: hero, ),- And as soon as we add the
ObjectKeywe can see the output. And all the things are working fine now. 
PageStorageKey
- The
PageStorageKeyis basically used to store the scroll position of the scrollable widgets likeListView,GridViewetc. - In some cases, we want to provide the functionality of storing the position of the scrolling list item and when users came back later to that scroll view they can start scrolling where they left.
- In this case, we can use
PageStorageKeyto preserve the state of the scrolling position. - Let's take an example to understand how we can use
PageStorageKeyin our app. Scaffold( body: ListView.builder( itemCount: 100, itemBuilder: (context, index) { return Padding( padding: const EdgeInsets.all(8.0), child: Text( "Item : $index", style: TextStyle(fontSize: 22), ), ); }, ), );- Here I've created a simple Listview

- Now let's go to another tab while leaving the scroll position in the middle.

- See? It's not preserving the position of the listview.
- Let's try to solve this issue by adding
PageStorageKeyin the ListView'skeyparameter ListView.builder( key: PageStorageKey<String>("listViewKey"), itemCount: 100, itemBuilder: (context, index) => ListTile( title: Text( 'List item ${index + 1}', style: TextStyle(fontSize: 24), ), ), );
- THAT'S!! That's all you have to do to store the scroll location. Flutter will handle all of the complicated things under the hood.
- BUT, what if you've popped that screen from the widget tree and then again visit this page? Because in the above case we're only going to another tab without popping the current page.
- In popped screen case, you'll not be able to get the previous position of the scroll view, because when you pop the screen flutter will also remove the
PageStorageKeyattached to it. 
- To solve this issue we need to wrap
PageStorageinside the parent widget of the widget tree. In our case, we can wrap it inside theScaffoldbecause therouteis created before the build final globalBucket = PageStorageBucket(); '''Don't declare it inside any class. Declare it on global level.'''Widget build(BuildContext context) { return PageStorage( bucket: globalBucket, child: Scaffold( bottomNavigationBar: BottomNavigationBar( backgroundColor: Theme.of(context).primaryColor, selectedItemColor: Colors.white, unselectedItemColor: Colors.white70, currentIndex: index, items: [ BottomNavigationBarItem( icon: Icon(Icons.list), title: Text('ListView'), ), BottomNavigationBarItem( icon: Icon(Icons.person), title: Text('Blah blah'), ), ], onTap: (int index) => setState(() => this.index = index), ), appBar: AppBar(), body: buildPages(), ), ); }- Now it is working

GlobalKey
- This is the most used Key in Flutter compare to the above Keys.
- The
GlobalKeycan be used to change the parents anywhere in your app without losing state - It can be used to access information about another widget when we are on a completely different location of the widget tree.
- The common use case of the
GlobalKeyis validating aFormor displaying theSnackbarin the app etc. - Let's take an example.
final _counterState = GlobalKey<_CounterState>(); //Declaring the GlobalKey of CounterState
Scaffold(
appBar: AppBar(),
body: SizedBox.expand(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Counter(
key: _counterState,
),
],
),
),
);
class Counter extends StatefulWidget { const Counter({ Key? key, }) : super(key: key); @override _CounterState createState() => _CounterState(); }class _CounterState extends State<Counter> { late int count; @override void initState() { super.initState(); count = 0; } @override Widget build(BuildContext context) { return Column( children: <Widget>[ Text( count.toString(), style: TextStyle(fontSize: 30), ), ElevatedButton( onPressed: () { setState(() { count++; }); }, child: Text("Add")) ], ); } }- Output

- Now we can access the
countvalue ofCounterWidgetin any page by passing theGlobalKey class SecondPage extends StatefulWidget { final GlobalKey<_CounterState> counterKey; SecondPage(this.counterKey); @override _SecondPageState createState() => _SecondPageState(); }class _SecondPageState extends State<SecondPage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), body: Center( child: Row( children: <Widget>[ IconButton( icon: Icon(Icons.add), onPressed: () { setState(() { widget.counterKey.currentState!.count++; //here print(widget.counterKey.currentState!.count); }); }, ), Text( widget.counterKey.currentState!.count.toString(), style: TextStyle(fontSize: 50), ), ], ), ), ); } }
- So as we can see GlobalKey can be used to access information about another widget when we are on a completely different location of the widget tree.
THAT'S IT
- That's all you need to know about Keys.
- Hope you liked it. Thanks for reading
Feedback and Comments are welcomed ๐






