Correct use of key in Flutter

1. What is a key

There is an optional attribute key in Widget. As the name suggests, it is the identifier of the component. When the key is set, the component will be updated according to whether the keys of the old and new components are equal, which can improve the update efficiency. But generally we don’t set it, unless we need to use the key when adding, removing, or sorting certain stateful and identical components, otherwise there will be some inexplicable problems.

For example, the following demo:

import 'dart:math';
import 'package:flutter/material.dart';

void main() { runApp(const MyApp());
}
class MyApp extends StatelessWidget {const MyApp({Key? key}) : super(key: key);@overrideWidget build(BuildContext context) {return MaterialApp(title: 'test',home: Scaffold(appBar: AppBar(title : const Text('key demo'),),body: const KeyDemo(),),),);}
}

class KeyDemo extends StatefulWidget {const KeyDemo({Key? key}) : super(key: key);@overrideState<StatefulWidget> createState() => _KeyDemo();
}

class _KeyDemo extends State<KeyDemo> {final List<ColorBlock> _list = [const ColorBlock(text: '1'), const ColorBlock(text: '2'), const ColorBlock(text: '3' ), const ColorBlock(text: '4'), const ColorBlock(text: '5'),]; @overrideWidget build(BuildContext context) {return Column(children: [..._list, ElevatedButton(onPressed : () {_list.removeAt(0);setState(() {});},child: const Text('remove'),)],);}
}

class ColorBlock extends StatefulWidget {final String text;const ColorBlock({Key? key, required this.text}) : super(key: key);@overrideState<StatefulWidget> createState() => _ColorBlock();
}

class _ColorBlock extends State<ColorBlock> {final color = Color.fromRGBO(Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1.0);@overrideWidget build(BuildContext context) {return Container(width: double.infinity,height: 50,color: color,child: Text(widget.text),);}
}

Click the delete button to delete the first element from the ColorBlock list. It can be observed that the color is disordered, the color block No. 1 is deleted, and its color state is transferred to No. 2. This situation often causes a lot of trouble in actual development.

At this time, you need to set the key value for each ColorBlock to avoid this problem.

final List<ColorBlock> _list = [const ColorBlock(key: ValueKey('1'), text: '1'),const ColorBlock(key: ValueKey('2'), text: '2'), const ColorBlock(key: ValueKey('3'), text: '3'), const ColorBlock(key: ValueKey('4'), text: '4\ '), const ColorBlock(key: ValueKey('5'), text: '5'),];

Click the delete button, you can see that the phenomenon of color confusion has disappeared, and everything is normal. So have you ever wondered why there is such a difference between ColorBlock with a key and without a key?

2. Key update principle

Let’s briefly analyze the key update principle.

First of all, we know that Widget is a description of component configuration information, and Element is the real implementation of Widget, which is responsible for the layout and rendering of components. When a Widget is created, a corresponding Element is created, and the Element stores the information of the Widget.

When we update a component (usually refers to calling the setState method), we will traverse the component tree and compare the new and old configurations of the component. If the information of the same component is inconsistent, the update operation will be performed, otherwise, no operation will be performed.

///Element
 Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {if (newWidget == null) {if (child != null)deactivateChild(child);return null;}final Element newChild;/// update logic Go here if (child != null) { bool hasSameSuperclass = true; if (hasSameSuperclass & amp; & amp; child.widget == newWidget) {if (child.slot != newSlot) updateSlotForChild(child, newSlot);newChild = child;} else if (hasSameSuperclass & amp; & amp; Widget.canUpdate(child.widget, newWidget)) {/// Judging that the old and new components are the same component, update if (child.slot != newSlot)updateSlotForChild( child, newSlot); child.update(newWidget); newChild = child;} else {deactivateChild(child);newChild = inflateWidget(newWidget, newSlot);if (!kReleaseMode & amp; & amp; debugProfileBuildsEnabled)Timeline.finishSync() ;}} else {/// Create logic here newChild = inflateWidget(newWidget, newSlot);
 }return newChild;}

Update the component through updateChild in Element, and Widget.canUpdate is the core to judge whether the component needs to be updated.

/// Widget
 static bool canUpdate(Widget oldWidget, Widget newWidget) {return oldWidget.runtimeType == newWidget.runtimeType & amp; & amp; oldWidget.key == newWidget.key;}

The code of canUpdate is very simple. It is to compare whether the runtimeType and key of the old and new components are consistent. The consistency just means that the same component needs to be updated.

Combined with the demo, when the delete operation is performed, the first component in the list, oldWidget, is ColorBlock(text: ‘1’), and newWidget is ColorBlock(text: ‘2’), because we store both text and color properties in State, So oldWidget.runtimeType == newWidget.runtimeType is true, and oldWidget.key == newWidget.key is null, which is also equal to true.

So call udpate to update

///Element
void update(covariant Widget newWidget) {_widget = newWidget;
}

It can be seen that update simply updates Element’s reference to Widget. Finally, the new widget is updated to ColorBlock(text: ‘2’), the State is still the State of ColorBlock(text: ‘1’), and the internal state remains unchanged.

If a Key is added, oldWidget.key == newWidget.key just now is false, and the update process will not go through, so this problem does not exist.

3. Key classification

key has two subclasses GlobalKey and LocalKey.

GlobalKey

GlobalKey is a globally unique key. It will not be rebuilt every time it is built. It can maintain the status of components for a long time. It is generally used to access the status of Widgets across components.

class GlobalKeyDemo extends StatefulWidget {const GlobalKeyDemo({Key? key}) : super(key: key);@overrideState<StatefulWidget> createState() => _GlobalKeyDemo();
}

class _GlobalKeyDemo extends State<GlobalKeyDemo> {GlobalKey _globalKey = GlobalKey();@overrideWidget build(BuildContext context) {return Column(children: [ColorBlock(key: _globalKey,),ElevatedButton(onPressed: () {/// Through GlobalKey Access the interior of the component ColorBlock (_globalKey.currentState as _ColorBlock).setColor();setState(() {});}, child: const Text('Update to red'),)],);}
}

class ColorBlock extends StatefulWidget {const ColorBlock({Key? key}) : super(key: key);@overrideState<StatefulWidget> createState() => _ColorBlock();
}

class _ColorBlock extends State<ColorBlock> {Color color = Colors.blue;setColor() {color = Colors.red;}@overrideWidget build(BuildContext context) {return Container(width: double.infinity,height: 50,color: color ,);}
}

Set the key of the component as GlobalKey, and you can access the internal properties and methods of the component through the instance. To achieve the purpose of cross-component operation.

LocalKey

LocalKey local key can maintain the status of subcomponents in the current component. Its usage is similar to GlobalKey and can access the data inside the component.

LocalKey has 3 subclasses ValueKey, ObjectKey, and UniqueKey.

  • ValueKey

Any value can be used as the key, and the comparison is whether the two values are equal or not.

class ValueKey<T> extends LocalKey {
 const ValueKey(this. value);
 final T value;

 @override
 bool operator ==(Object other) { if (other.runtimeType != runtimeType) return false; return other is ValueKey<T> & amp; & amp; other.value == value;
 }
/// ...
}
  • ObjectKey:

You can use the Object object as the Key to compare whether the memory addresses of the two objects are the same, that is to say, whether the two objects are referenced from the same class.

class ObjectKey extends LocalKey {const ObjectKey(this.value);final Object? value;@overridebool operator ==(Object other) {if (other.runtimeType != runtimeType)return false;/// identical function: check Whether the two references point to the same object return other is ObjectKey & amp; & amp; identical(other.value, value);}/// ...
}
  • UniqueKey

Unique key, the uniqueness of Key, once UniqueKey is used, there will be no element reuse

class UniqueKey extends LocalKey {UniqueKey();@overrideString toString() => '[#${shortHash(this)}]';
}

Summary

1. The key is the unique identifier in the Widget. If the list contains stateful components, the key must be added to add, remove, or sort them. to avoid confusion.

2. The root cause of the out-of-order phenomenon is: the old and new components are compared with the runtimeType and key. When the key is empty, the runtimeType comparison of the stateful component is true, causing the component to still maintain the internal property state of the State after the component is updated.

3. The key is divided into GlobalKey and LocalKey. GlobalKey can access Widget across components, and LocalKey can only be accessed under the same level.

Last

I recently found a VUE document, which summarizes the various knowledge points of VUE and organizes it into “36 skills that Vue development must know”. The content is relatively detailed, and the explanation of each knowledge point is also in place.



For friends in need, you can click the card below to receive and share for free