The definition of the Key

Let’s take a look at the official definition of Key:

A Key is an identifier for Widgets, Elements and SemanticsNodes. A new widget will only be used to update an existing element if its key is the same as the key of the current widget associated with the element. www.youtube.com/watch?v=kn0EOS-ZiIc Keys must be unique amongst the Elements with the same parent. Subclasses of Key should either subclass LocalKey or GlobalKey. @immutable abstract class Key { /// Construct a [ValueKey<String>] with the given [String]. /// /// This is the simplest way to create keys.  const factory Key(String value) = ValueKey<String>; /// Default constructor, used by subclasses. /// /// Useful so that subclasses can call us, because the [new Key] factory /// constructor shadows the implicit constructor. @protected const Key.empty(); }Copy the code

Three Key pieces of information about keys were extracted:

Key is the identifier of Widgets, Elements, and SemanticsNodes. 2. The new widget is used to update an existing element only if its key is the same as the key of the current widget associated with the element. 3. The key must be unique in elements whose parent element is the same.

Key is derived from two different types of keys: LocalKey and GlobalKey. A subclass of Key should be a subclass of LocalKey or GlobalKey.

  • Localkey

The LocalKey directly inherits from the Key and is used to compare widgets that have the same parent widget. For example, if a widget has multiple child widgets, the LocalKey should be used to move its child widgets.

Localkey has a number of subclasses of key:

  • ValueKey : ValueKey(‘String’)
  • ObjectKey : ObjectKey(Object)
  • UniqueKey : UniqueKey()

Valuekey is derived from PageStorageKey

  • GlobalKey
@optionalTypeArgs abstract class GlobalKey<T extends State<StatefulWidget>> extends Key { /// Creates a [LabeledGlobalKey], which is a [GlobalKey] with a label used for /// debugging. /// /// The label is purely for debugging and not used for comparing the identity /// of the key. factory GlobalKey({ String? debugLabel }) => LabeledGlobalKey<T>(debugLabel); /// Creates a global key without a label. /// /// Used by subclasses because the factory constructor shadows the implicit /// constructor. const GlobalKey.constructor() : super.empty(); Element? get _currentElement => WidgetsBinding.instance! .buildOwner! ._globalKeyRegistry[this]; /// The build context in which the widget with this key builds. /// /// The current context is null if there is no widget in the tree that matches /// this global key. BuildContext? get currentContext => _currentElement; /// The widget in the tree that currently has this global key. /// /// The current widget is null if there is no widget in the tree that matches /// this global key. Widget? get currentWidget => _currentElement? .widget; /// The [State] for the widget in the tree that currently has this global key. /// /// The current state is null if (1) there is no widget in the tree that /// matches this global key, (2) that widget is not a [StatefulWidget], or the /// associated [State] object is not a subtype of `T`. T? get currentState { final Element? element = _currentElement; if (element is StatefulElement) { final StatefulElement statefulElement = element; final State state = statefulElement.state; if (state is T) return state; } return null; }}Copy the code

You can find the Widget, State, and Element that hold the GlobalKey.

The key must be unique in elements that have the same parent element. In contrast, GlobalKey must be unique across the entire application.

Note: GlobalKey is very expensive and should be used with caution.

Widget’s diff update mechanism

Widgets can be Stateful or Stateless. Each Widget constructor has an optional parameter Key, which is the Widget’s identifier and helps developers save state in the Widget tree.

Let’s use a demo to illustrate how keys work with widgets

class StatelessDemo extends StatelessWidget { final randomValue = Random().nextInt(10000); @override Widget build(BuildContext context) { // TODO: implement build return Text('$randomValue'); }}Copy the code

This is a very simple Stateless Widget that displays a random number on the interface. Random().nextint (10000) initializes a Random number less than 10000 for the Widget.

Display the Widget on the screen:

class MyHomePage extends StatefulWidget { @override State<StatefulWidget> createState() { // TODO: implement createState return _MyHomePageState(); } } class _MyHomePageState extends State<MyHomePage> { List<StatelessDemo> widgetArr = [StatelessDemo(), StatelessDemo()]; @override Widget build(BuildContext context) { // TODO: implement build return Padding( padding: EdgeInsets.only(top: 100), child: Column( children: [ widgetArr[0], SizedBox(height: 50), widgetArr[1], SizedBox(height: 80), TextButton( onPressed: () { setState(() { widgetArr.insert(0, widgetArr.removeAt(1)); }); }, child: Text(' swap widget location ')],),); }}Copy the code

There are two StatelessDemo components displayed in the interface, and when we click TextButton, we will perform the operation of exchanging their order.

Now let’s make a small change to upgrade this StatelessDemo to StatefulDemo:

class StatefulDemo extends StatefulWidget { @override State<StatefulWidget> createState() { // TODO: implement createState return StatefulDemoState(); } } class StatefulDemoState extends State<StatefulDemo> { final randomValue = Random().nextInt(10000); @override Widget build(BuildContext context) { // TODO: implement build return Text('$randomValue'); }}Copy the code

In State Fuldemo, we put the defined Random and build methods into State.

Now let’s use the same layout as before, but replace StatelessDemo with StatefulDemo and see what happens.

At this point, no matter how we click, there is no way to exchange the order of the two widgets any more, and setState is actually implemented.

To solve this problem, we pass a UniqueKey to both widgets when they are constructed:

class _MyHomePageState extends State<MyHomePage> { List<StatefulDemo> widgetArr = [ StatefulDemo(key: UniqueKey()), StatefulDemo(key: UniqueKey()) ]; 、、、、、、、、、}Copy the code

The two widgets can then be exchanged in normal order again.

The ejbateful Widget could not exchange the order properly, but the ejbateful Widget was added with a Key. What happened to the ejbateful Widget? To understand this, we will cover the Widget’s diff update mechanism.

/// Whether the `newWidget` can be used to update an [Element] that currently /// has the `oldWidget` as its configuration. /// /// An element that uses a given widget as its configuration can be updated to /// use another widget  as its configuration if, and only if, the two widgets /// have [runtimeType] and [key] properties that are [operator==]. /// /// If the widgets have no key (their key is null), then they are considered a /// match if they have the same type, even if their children are completely /// different. static bool canUpdate(Widget oldWidget, Widget newWidget) { return oldWidget.runtimeType == newWidget.runtimeType && oldWidget.key == newWidget.key; }Copy the code

The canUpdate method is called when a new Widget arrives to determine if the Element needs to be updated. The Widget is just a configuration and cannot be modified, whereas the Element is the object that is actually used and can be modified. 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.

  • StatelessDemo comparison procedure:

In StatelessDemo, we don’t pass in keys, so we just compare their runtimeTypes. In this case, the runtimeType is the same, the canUpdate method returns true, the two widgets are swapped, and the two elements will not be swapped. Element calls the build method that holds the new Widget to rebuild. Our randomValue is actually stored in the widget, so the two widgets are swapped in the correct order on the screen.

  • StatefulDemo Comparison procedure:

We will randomValue StatefulDemo, in the State, the definition of a Widget doesn’t save State, really hold State reference is Stateful Element

When we do not give the Widget any key, only the runtimeType of the two widgets will be compared. Since both widgets have the same runtimeType, the canUpdate method will return true. The two StatefulWidgets will then swap places, and notice that the two elements will not swap places. The original Element will only be rebuilt from the build method of the widget it holds. Since the definition of randomValue is placed in State, randomValue will not be swapped. Because randomValue is held by the State and not the widget.

When a key is given to a Widget, the canUpdate method compares the runtimeType and key of the two widgets. The two widgets have the same runtimeType but different keys, so false is returned. Because canUpdate returns false, instead of updating with the current widget, a new Element is created based on the current widget. To create a new Element, state is recreated and the definition of randomValue is placed in the state. It looks like the two elements have swapped.

When do YOU need to use keys

  • ValueKey: Used when sliding an item from the ListView

  • ObjectKey: If you have a phone book app, it can record someone’s phone number and display it in a list, again with a swipe delete operation.

We know that the name may be repeated, and there’s no guarantee that you’ll give the Key a different value every time. However, Object will be unique when the name and phone number are combined.

So you need to use ObjectKey.

  • UniqueKey: If none of the combined objects can be unique, you want to ensure that each Key is unique. Well, you can use UniqueKey. It will generate a unique hash code from the object. However, every time a Widget is built, a new UniqueKey will be generated, losing consistency, which means your Widget will still change.

  • PageStorageKey: Used to save the status of a page. For example, when you have a sliding list, you jump to a new page from an Item. When you return to the previous list page, you find that the sliding distance is back to the top. At this point, give the Sliver a PageStorageKey! It will keep the Sliver scrolling.

  • GlobalKey: GlobalKey is used to access state across widgets.

reference

Official video – Explain Key