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

Search for a command to run...
All you need to know about Keys in Flutter

Bro.. i love your content.. i don't like things easily
Thanks Gopal for the feedback ๐ Glad that you loved it ๐
Thanks PHAM BA MUOI
Thanks Tada Nguyen. Glad you liked it ๐
Lessons from Code, Content, and Consistency

Reliability basics: Outbox pattern + why it matters

Introducing Kafka/Redpanda + move to event-driven workflow

Connect Orders โ Inventory (first working service-to-service flow)

Inventory service + gRPC + proto contracts

key parameter in all of those widgets.Keys. And if they do, they don't know how to use them or where to put them.Keys has very few use cases or you can say their use is less common.Keys, and different types of Keys, Where to use Keys, and How to use them.
Key??Keys written in Flutter's Official Documentation, It says :A Key is an identifier for Widgets, Elements, and SemanticsNodes.
Keys. But it's more than that.Keys preserves the state when 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.
UniqueKey in Flutter is used to identify every widget of your app uniquely.UniqueKey also preserves the state when widgets move around in your widget tree.UniqueKey can be used in cases like when you are reordering the widget in the list or adding or removing the widgets from a list.UniqueKey which will assign a unique key to that particular widget.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,
),
);
}
}

But the problem will come if we try to convert the Stateless widget into Stateful widget and store the value in a state.
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,
),
);
}
}


type of the widget (runtimeType) and by the keys.Stateful widgets in our list (one for ๐, and one for ๐ค ). when we swap the emojis positions, by pressing the swap button, flutter will then check in the ElementTree that, Is the type of the changed widget is the same as the type of the ElementTree's element or not?.ElementTree, The ElementTree only 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.

ElementTree, checks the type of the RowWidget, and updates the reference. After that, it checks if the type of ๐ Text Element of the ElementTree is same as ๐ค Text Widget's type? and it is, so it updates the reference. And nothing will update.Stateful widgets differently. Then there will be no problem. Because both will then have different IDs/keys assigned.UniqueKey to all the widgetclass 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(),
),
];

type, its gets matched. But when it is trying to match keys it will not match. And in the element tree, as keys are not matching, it will change the references and update the app.Swapping widget

Keys not matched



Keystop of the widget subtree. Otherwise, you'll get some weird results.
GetEmoji widget with Container widget. Now observe here UniqueKey is not at the top of its widget tree.List<Widget> emojis = [
Container(
child: GetEmoji(
emg: "๐",
key: UniqueKey(),
),
),
Container(
child: GetEmoji(
emg: "๐ค ",
key: UniqueKey(),
),
),
];

Text Widget is generating again and again in the widget tree, we are not able to see that because we're using two static emojis only. Keys to the widget RIGHT? But it's not about the keys which are creating a problem it's about the position of the keys.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.
๐ Container Element doesnโt match the key of the widget, so it deactivates that ๐ Container Element, dropping those connections. 
LocalKeys. That means that when matching up widgets to elements, Flutter only looks for key matches within a particular level in the tree.Container Element at that level with that key value, it creates a new one, and initializes a new state, in this case, making the widget with the random background color.
key in the Padding widget.ValueKey :value of a particular type to identify itself.ValueKey is useful if we want to preserve the state of the Stateful widgets when they move around the widget tree.ValueKey when we want to remove Widget from the widget tree, or reordering the list.Textfield widget. And we want to remove the last Textfield from 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"),
)
],
),

Remove Favourite Framework field button.
Text of Favourite Framework's Textfield i.e Flutter in the Textfield of Favourite language's Textfield instead of Dart.ValueKey.TextField(
key: ValueKey("Framework"),
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: "Favourite Framework"
),
),
TextField(
key: ValueKey("Language"),
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: "Favourite Language"
),
),

keys are of the same type or not, and it's not. So flutter will update the state and references accordingly.ValueKey you 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.
ObjectKeyObjectKey is useful if we want to preserve the state of the Stateful widgets when they move around the widget tree.ObjectKey can be used in cases like when you are reordering the widget in the list or adding or removing the widgets from a list.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(),
),
),
);
superHeroList.
ValueKey, Right?. And yes you're right we can use ValueKey to distinguish the list widgets. But there will be an issue. Let's see what happen if we consider the ValueKey in this situation.ValueKey to key parameter.Center(
child: Column(
children: superHeroList
.map<Widget>(
(hero) => HeroWidget(
key: ValueKey(hero),
hero: hero,
),
)
.toList(),
),
),
-BUT...but, Now add the same Object in 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"),
];



ValueKey explanation is that the `widget is identified by its value when we use ValueKeyObjectKey.ObjectKey, that ObjectKey will distinguish the item based on the references.ObjectKey in the key parameter.HeroWidget(
key: ObjectKey(hero),
hero: hero,
),
ObjectKey we can see the output. And all the things are working fine now.
PageStorageKey is basically used to store the scroll position of the scrollable widgets like ListView, GridView etc.PageStorageKey to preserve the state of the scrolling position.PageStorageKey in 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),
),
);
},
),
);


PageStorageKey in the ListView's key parameterListView.builder(
key: PageStorageKey<String>("listViewKey"),
itemCount: 100,
itemBuilder: (context, index) => ListTile(
title: Text(
'List item ${index + 1}',
style: TextStyle(fontSize: 24),
),
),
);

PageStorageKey attached to it.
PageStorage inside the parent widget of the widget tree. In our case, we can wrap it inside the Scaffold because the route is created before the buildfinal 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(),
),
);
}

GlobalKey can be used to change the parents anywhere in your app without losing stateGlobalKey is validating a Form or displaying the Snackbar in the app etc.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"))
],
);
}
}

count value of CounterWidget in any page by passing the GlobalKeyclass 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),
),
],
),
),
);
}
}

Feedback and Comments are welcomed ๐
