In development, we will find that whenever we create a widget, we have a parameter key. What does this key do? Here’s what key is, what it does, and how to use it.
What is the key
We often deal with states in Flutter development. Widgets are known to be Stateful and Stateless. A Key helps developers save state in the Widget tree, and in general, we don’t need a Key. So when exactly should you use Key?
Here’s an example:
- Created a 100 × 100 box with randomly generated colors and text that was passed in from the outside
class StfulItem extends StatefulWidget { final String title; const StfulItem(this.title, {Key? key}) : super(key: key); @override _StfulItemState createState() => _StfulItemState(); } class _StfulItemState extends State<StfulItem> {final color = color.fromrgbo (Random().nextint (256), The Random (). NextInt (256), Random () nextInt (256), 1.0); @override Widget build(BuildContext context) { return Container( width: 100, height: 100, color: color, child: Text(widget.title), ); }}Copy the code
- Create three boxes created above, and then delete the first box when you click the button
class _KeyDemoState extends State<KeyDemo> { List<Widget> items = [ const StfulItem('1111'), const StfulItem('2222'), const StfulItem('3333'), ]; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text("KeyDemo"),), body: Row( mainAxisAlignment: MainAxisAlignment.center, children: items, ), floatingActionButton: FloatingActionButton( child: const Icon(Icons.delete), onPressed: (){ setState(() { items.removeAt(0); }); },),); }}Copy the code
Put color generation out of state
class StfulItem extends StatefulWidget { final String title; StfulItem(this.title, {Key? key}) : super(key: key); Final color = color.fromrgbo (Random().nextint (256), Random().nextint (256), Random().nextint (256), 1.0); @override _StfulItemState createState() => _StfulItemState(); }Copy the code
The effects were also deleted one by one, and no color reuse was found, because this was caused by updating the widget.
Change the StatefulWidget above to a StatelessWidget and see what it looks like.
class StlItem extends StatelessWidget { final String title; StlItem(this.title, {Key? key}) : super(key: key); Final color = color.fromrgbo (Random().nextint (256), Random().nextint (256), Random().nextint (256), 1.0); @override Widget build(BuildContext context) { return Container( width: 100, height: 100, child: Text(title), color: color, ); }}Copy the code
It was found that it was normal after deletion at this time, and the color of the previous one was not reused.
- For the first case, the previous color will be reused, if used
value Key
What would be the effect?
List<Widget> items = [
StfulItem('1111', key: const ValueKey('1111')),
StfulItem('2222', key: const ValueKey('2222')),
StfulItem('3333', key: const ValueKey('3333')),
];
Copy the code
Found that did achieve the effect, there is no reuse, here I will not paste the operation of the amount of effect.
Why Stateful Widget could not be removed properly after a Key was added? What happened in the container? To understand this, we’ll cover the Widget’s diff update mechanism. Let’s talk about how Flutter renders
Flutter rendering principle
Not all widgets are rendered independently! Only those who inherit from RenderObjectWidget will create RenderObject objects. There are three important trees in the Flutter rendering process! The Flutter engine renders against the Render tree!
Widgets: Widgets store configuration information about a view, including layout, properties, and so on. It is a lightweight data structure, built as a structure tree, that does not involve direct drawing.
Element: Element is an abstraction of a Widget. When a Widget is first created, the Widget creates an Element that is mounted to the Element Tree to traverse the view Tree. In attachRootWidget function, put the widget to the bridge RenderObjectToWidgetAdapter, Element created at the same time also holds a widget and RenderObject references. The build system creates the RenderObject by traversing the Element Tree. Each Element has a unique key, and when a view update is triggered, only the marked Element that needs to be changed is updated. React updates the virtual DOM tree after setState.
RenderObject: Layout and Paint events occur in the RenderObject Tree, where most of the optimization of drawing performance occurs. The RenderObject Tree is constructed as the required Canvas description data and added to the Layer Tree. Finally, the view was synthesized in the Flutter Engine and rasterized to the GPU.
Widget update mechanism
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
Copy the code
Now that we know how Flutter renders, we know that the Widget is only a configuration and cannot be modified, whereas the Element is the object that is actually used and can be modified. The canUpdate method is called when a new Widget arrives to determine if the Element needs to be updated. CanUpdate compares the runtimeType and key of two (old and new) widgets to determine whether the current Element needs to be updated. If the canUpdate method returns true, you don’t need to replace the Element, just update the Widget.
The comparison of Stateless
We passed no key when using the StatelessWidget and the runtimeType is consistent, so canUpdate returns true. In this case, the Widget’s build method needs to be rebuilt. Since both the color and title are inside the Widget, you should see the normal removal effect.
The comparison of Stateful
The Widget does not hold state, but rather refers to Stateful Element. We use the StatefulWidget without a pass-through key, and the runtimeType is consistent, so canUpdate returns true to update the widget, and the Element of the StatefulWidget will reuse the previous one.
When we pass a value key, canUpdate returns false. The owners of Flutter will think that this Element needs to be replaced. Then a new Element object is generated and loaded into the Element tree to replace the original Element.
Several types of keys
Key itself is an abstract class with a factory constructor to create ValueKey
-
Direct subclasses include LocalKey and GlobalKey
-
GlobalKey: Helps us access information about a Widget
-
LocalKey: This is used to distinguish which Element to keep and which Element to remove. The heart of the DIff algorithm
- ValueKey: Takes a value as an argument (number, string, any type)
- ObjectKey: Takes an object as an argument
- LocalKey :(create unique identifier)
Three types of LocalKey
LocalKey, which is inherited from Key, must be unique within an Element that has the same parent. That is, LocalKey must be unique within the same hierarchy.
ValueKey
bool operator ==(Object other) { if (other.runtimeType ! = runtimeType) return false; return other is ValueKey<T> && other.value == value; }Copy the code
Use a value of a specific type to identify its own key. ValueKey is already used in the top example. It can accept an object of any type as a key.
== == == == == == == == == == =
ObjectKey
bool operator ==(Object other) { if (other.runtimeType ! = runtimeType) return false; return other is ObjectKey && identical(other.value, value); }Copy the code
The main difference between ObjectKey and ValueKey is that the comparison is not the same. First, it is the comparison type, and then the indentical method is called to compare the memory address, which is equivalent to using == directly in Java to compare. LocalKey is the equivalent of Java’s equals method for comparing values;
UniqueKey
bool operator ==(Object other) { if (other.runtimeType ! = runtimeType) return false; return other is ObjectKey && identical(other.value, value); }Copy the code
Each time you rebuild, the UniqueKey is unique, so the corresponding Element cannot be found and the state is lost. One way to do this is to define the UniqueKey outside the build so that there is no loss of state.
GlobalKey
GlobalKey inherits from Key, which is global in scope compared to LocalKey, which is only at the current level. This is unique throughout the application, so the same GlobalKey can only be used on one widget. You can use GlobalKey to retrieve the corresponding state and element or widget to change the state or variable value.
Code examples:
Modify the value of the content by clicking the hover button
class GlobalKeyDemo extends StatelessWidget { GlobalKeyDemo({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('GlobalKeyDemo'), ), body: ChildPage(), floatingActionButton: FloatingActionButton( child: const Icon(Icons.add), onPressed: (){ }, ), ); } } class ChildPage extends StatefulWidget { const ChildPage({Key? key}) : super(key: key); @override _ChildPageState createState() => _ChildPageState(); } class _ChildPageState extends State<ChildPage> { int count = 0; String data = 'hello'; @override Widget build(BuildContext context) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(count.toString()), Text(data), ], ), ); }}Copy the code
If the widget is not in the same widget, you will not be able to modify the text text in ChildPage. In this case, use GlobalKey
Class GlobalKeyDemo extends StatelessWidget {// define GlobalKey final GlobalKey<_ChildPageState> _globalKey = GlobalKey(); GlobalKeyDemo({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Body: ChildPage(key: _globalKey,), floatingActionButton: const Text('GlobalKeyDemo'),), // Upload the global key to the other component. FloatingActionButton( child: const Icon(Icons.add), onPressed: (){ _globalKey.currentState! .setState(() { _globalKey.currentState! .data = 'old:' + _globalKey.currentState! .count.toString(); _globalKey.currentState! .count++; }); },),); } } class ChildPage extends StatefulWidget { const ChildPage({Key? key}) : super(key: key); @override _ChildPageState createState() => _ChildPageState(); } class _ChildPageState extends State<ChildPage> { int count = 0; String data = 'hello'; @override Widget build(BuildContext context) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(count.toString()), Text(data), ], ), ); }}Copy the code
We have the following apis for retrieving content via Global Key:
- CurrentContext: You can find various elements including renderBox
- CurrentWidget: Gets the properties of the widget
- CurrentState: You can get variables in state.
The implementation principle of Global Key
Inside GlobalKey there is a static _Registry Map collection with GlobalKey as the key and Element as the Value. The currentSate method takes the GlobalKey object as its key and fetches the corresponding Element object, which can then be retrieved using element.state.
I don’t know why GlobalKey is very expensive. There are no specific reuse requirements and it is not recommended to use GlobalKey.
Effect:
summary
Key should be used when you want to preserve state across widget trees. When modifying a collection of widgets of the same type, place the key at the top of the tree of the widgets you want to keep. LocalKey must be unique at the same level; GlobalKey is global in scope.
-
GlobalKey: Helps us access information about a Widget
-
LocalKey: This is used to distinguish which Element to keep and which Element to remove.
- ValueKey: Takes a value as an argument (number, string, any type)
- ObjectKey: Takes an object as an argument
- LocalKey :(create unique identifier)
Reference article:
Flutter rendering principle: zhuanlan.zhihu.com/p/135969091
Flutter | covering the Key: juejin. Cn/post / 684490…