Introduction
Flutter is an open source framework for multi-platform applications. In Flutter, keys are often overlooked until you encounter strange issues with data rendering. Keys are objects used to distinguish widgets in the widget hierarchy, allowing Flutter to update and manage the user interface efficiently. Let me give you some guidance to the types of keys used in Flutter to help you succeed with this software
Types of keys
Value Key
Value key’s are commonly used in a list of children with the condition that the values of the child elements are unique and constant.
List<String> items = ['Apple','Banana','Orange'];
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
// Using ValueKey with item value
key: ValueKey(items[index]),
title: Text(items[index]),
);
},
);
Object Key
Similar to the ValueKey, the ObjectKey takes its identity from the object used as its value. Additionally, it is often used with class objects that holds data.
List<String> items = List.generate(10, (index) => 'Item $index');
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return MyListItem(
key: ObjectKey(items[index]), // Using ObjectKey with the item's value
item: items[index],
);
},
);
}
}
Note:
The difference between ValueKey and ObjectKey lies in their comparison mechanisms. ValueKey uses the operator for comparison, whilst the ObjectKey checks if its internal object value is as identical (referring to the same instance) as the other.
This distinction is the reason why ObjectKey is recommended for use with class objects. When dealing with complex class instances, it is often more appropriate to use ObjectKey because it relies on object identity rather than the equality operator. This ensures that two different instances of the same data are not considered equal unless they are the same instance, which can be crucial in scenarios where maintaining identity is essential.
Unique Key
“A key that is only equal to itself.”
It cannot be created with a const constructor because doing so would generate keys with a similar identity, thus making them no longer unique.
In the situation where the children do not have unique values or don’t have a value at all, the Uniquekey is used to identify each child.
Widget build(BuildContext context) {
return Container(
// Using UniqueKey to force recreation of the widget
key: UniqueKey(),
child: // …
);
}
Global Key
Unlike a ValueKey, ObjectKey, and UniqueKey that are subclasses of LocalKey, this makes a widget unique throughout the entire application rather than a specific area. It is often used to invoke a method in which a child widget or access the state of a specific widget from anywhere in the program.
It does have a few good and valid use cases such as Form validation. When assigned to a Form, one can access the FormStates public functions and variables and use them accordingly.
final formKey = GlobalKey<FormState>();
Form(
//assign the globalkey to the key value of the form widget
key: formKey,
child: ListView(
children: [
//...
TextButton(
onPressed: () {
//access the Form widgets variables and functions
//in this case we use the validate() method
if (!formKey.currentState!.validate()) {
print('error in the form');
return;
}
},
child: Text('Validate'))
],
),
),
Use cases of Keys in Flutter
Widget Identity and Preservation
Keys help differentiate between different instances of similar widgets. When a widget is rebuilt, a new instance of the widget is created, but using keys allows Flutter to recognize that the widgets are similar and preserve their state. This is particularly useful when working with “StatefulWidget”.
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return Dismissible(
key: Key(items[index]), // Using Key to identify each item
onDismissed: (direction) {
removeItem(index);
},
background: Container(
color: Colors.red,
child: Icon(Icons.delete, color: Colors.white),
),
child: ListTile(
title: Text(items[index]),
),
);
},
)
In this example, each “Dismissible” widget is assigned a “Key” using the value of the corresponding item. This ensures that each item in the list is uniquely identified.
Efficient Widget Reordering
When dealing with lists of widgets, keys help Flutter efficiently rearrange the widgets without having to rebuild the entire list.
By providing a key for each item in the list, Flutter can identify and move widgets to their new positions without unnecessary widget reconstruction.
List<String> items = List.generate(5, (index) => 'Item $index');
ReorderableListView(
onReorder: (oldIndex, newIndex) {
setState(() {
if (oldIndex < newIndex) {
newIndex -= 1;
}
final item = items.removeAt(oldIndex);
items.insert(newIndex, item);
});
},
children: items
.map((item) => ListTile(
key: Key(item), // Using Key to identify each item
title: Text(item),
leading: Icon(Icons.drag_handle),
))
.toList(),
);
In this example:
- The ReorderableListView widget is used to create a list that allows items to be reordered by dragging.
- Each ListTile has a unique Key assigned to it using the value of the corresponding item.
- The onReorder callback is triggered when an item is reordered. It updates the state by swapping the positions of the items in the list.
- The Key ensures that Flutter can efficiently update the widget tree when the order of items changes, preserving the state of each item.
Preserving state in animated widgets
When you work with animated widgets such as “Animatedlist,
Animatedbuilder,
or AnimatedContainer
“, it is really important to ensure them operate. In this case, Keys allow Flutter to maintain the state during animations.
Without keys, animations might behave unexpectedly or reset their progress during rebuilds.
final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
List<String> items = ['Item 1', 'Item 2', 'Item 3'];
@override
Widget build(BuildContext context) {
return Column(
children: [
ElevatedButton(
onPressed: () {
addItem();
},
child: Text('Add Item'),
),
AnimatedList(
key: _listKey,
initialItemCount: items.length,
itemBuilder: (context, index, animation) {
return buildAnimatedListItem(items[index], animation);
},
),
],
);
}
Widget buildAnimatedListItem(String item, Animation<double> animation) {
return FadeTransition(
opacity: animation,
child: ListTile(
title: Text(item),
// Add other widget properties or behaviors as needed
),
);
}
void addItem() {
setState(() {
final newIndex = items.length;
items.add('Item $newIndex');
_listKey.currentState?.insertItem(newIndex);
});
}
}
In this example:
- An AnimatedList is used with a GlobalKey to manage the state of the animated list.
- Each item in the list is represented by a FadeTransition to achieve a fade-in effect when added and a fade-out effect when removed.
- The addItem function adds a new item to the list and triggers the animation for the new addition.
- The animations are handled automatically through the AnimatedListState.
Conclusion
In conclusion, keys are essential in Dart and Flutter for managing widget identity, preserving state, and optimizing performance. They offer significant advantages in managing widget trees and maintaining widget state during rebuilds, reordering, and animations. However, using keys requires careful consideration to ensure uniqueness and proper integration with the app’s architecture. When used correctly, keys play a crucial role in building efficient and responsive Flutter applications. For more IT insights and hints, check out here!
Thanh Nguyễn, Mobile Engineer