Hi πŸ‘‹

  • Wechat: RyukieW
  • πŸ“¦ Archive of technical articles
  • πŸ™ making
My personal project Minesweeper Elic Endless Ladder Dream of books Privacy Access Record
type The game financial tool
AppStore Elic Umemi Privacy Access Record

More columns:

The independent development of Lawliet

Lawliet’s iOS garden party

Lawliet’s underlying iOS lab

Lawliet’s iOS Reverse lab

Lawliet’s brush book

Lawliet’s Flutter Lab

The Key role

Let’s create an example like this to see what happens:

import 'dart:math';

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: HomePage(), ); }}class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  // the key is very important.
  List<Widget> items = [
    ColorItem('The first'),
    ColorItem('Second'),
    ColorItem('Third')];@override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Key's role '),
      ),
      body: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: items,
      ),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.add),
        onPressed: () {
          setState(() {
            items.removeAt(0); }); },),); }}class ColorItem extends StatefulWidget {
  final String title;

  ColorItem(this.title, {Key? key}) : super(key: key);

  @override
  _ColorItemState createState() => _ColorItemState();
}

class _ColorItemState extends State<ColorItem> {
  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(widget.title), color: color, ); }}Copy the code

The runtime found the above phenomenon, and as those of us with iOS development experience, it is easy to imagine that this phenomenon should be related to reuse.

Reuse problem

Here we need to understand that the ColorItem as a StatefulWidget is made up of two parts: Widget and State. State is still in memory after the Widget is removed. So there is the problem of reuse exceptions.

So putting data in widgets shouldn’t be the case according to the theory analyzed here, right? Let’s try:

class ColorItem extends StatefulWidget {
  final String title;
  // Move to Widget
  final color = Color.fromRGBO(
      Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1.0);

  ColorItem(this.title, {Key? key}) : super(key: key);

  @override
  _ColorItemState createState() => _ColorItemState();
}

class _ColorItemState extends State<ColorItem> {
  @override
  Widget build(BuildContext context) {
    return Container(
      width: 100,
      height: 100, child: Text(widget.title), color: widget.color, ); }}Copy the code

Verification successful! So what are the specific reasons?

Incremental rendering with canUpdate

In Widget implementation, the canUpdate method determines whether a Widget’s Element will be updated, which in turn is directly related to incremental rendering.

Both conditions are satisfied:

  • The old and newWidgetOf the same type
  • The old and newWidget ηš„ KeyThe same
    • ifKeyThey are empty, judged by type only, even if they are childrenWidgetCompletely different

This is a good explanation of what happened in the first place.

/// 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 illustration

Widgets and Elements correspond one to one, and State is in the Element.

  • To remove aWidgetStart to check
  • The first oneElementCheck the second oneWidget
    • callcanUpdate
      • The same type
      • KeyIs empty
      • return ture
    • And then useThe first ElementupdateThe second Widget
  • By analogy, the previous anomaly occurs

Since the Key is mentioned here, can the above problem be solved by adding Key?

The use of the Key

Here we add a Key to each ColorItem and put the Color attribute back into State:

.class _HomePageState extends State<HomePage> {
  // the key is very important.
  List<Widget> items = [
    ColorItem(
      'The first',
      key: ValueKey(1),
    ),
    ColorItem(
      'Second',
      key: ValueKey(2),
    ),
    ColorItem(
      'Third',
      key: ValueKey(3),)];@overrideWidget build(BuildContext context) { ... }}class ColorItem extends StatefulWidget {
  final Stringtitle; . }class _ColorItemState extends State<ColorItem> {
  final color = Color.fromRGBO(
      Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1.0); . }Copy the code

Effective!

Key

Key itself is an abstract class with a factory method:

/// A [Key] is an identifier for [Widget]s, [Element]s and [SemanticsNode]s.
///
/// 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.
///
/// {@youtube 560 315 https://www.youtube.com/watch?v=kn0EOS-ZiIc}
///
/// Keys must be unique amongst the [Element]s with the same parent.
///
/// Subclasses of [Key] should either subclass [LocalKey] or [GlobalKey].
///
/// See also:
///
///  * [Widget.key], which discusses how widgets use keys.
@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
  • It has two subclasses
    • LocalKey
      • The sameThe parent ElementThe only
    • GlobalKey
      • The wholeAppThe only

The ValueKey we just used is a subclass of LocalKey.

LocalKey

Distinguish which Element to keep and which Element to remove.

/// A key that is not a [GlobalKey].
///
/// Keys must be unique amongst the [Element]s with the same parent. By
/// contrast, [GlobalKey]s must be unique across the entire app.
///
/// See also:
///
///  * [Widget.key], which discusses how widgets use keys.
abstract class LocalKey extends Key {
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
  const LocalKey() : super.empty();
}
Copy the code
  • ValueKey
    • Take values as arguments (numbers, strings, etc.)
  • ObjectKey
    • Take an object as a parameter
  • UniqueKey
    • Create a Unique identifier

GlobalKey

Unique Key in the entire App. GlobalKey can uniquely identify an element, such as accessing a BuildContext or Widget. For StatefulWidget, GlobalKey also has access to State.

When widgets with GlobalKey are moved to a new location in the Widget tree, their subtrees are re-rendered.

To render a subtree, a Widget must be completed in the tree within the same animation frame, moving from the old position to the new position.

Rerendering an Element that uses GlobalKey is costly because it triggers calls to all the associated State deactivate methods and then rebuilds all the inheritedWidgets that depend on them.

So if you don’t need to achieve the above results, use a different Key.

Pay attention to the point

  • Both of them are in the same treeWidgetYou can’t have the sameGlobalKey. Attempting to do so will raise an exception at run time.
  • GlobalKeysNot every timebuildIs recreated. They should be held by a State for a long time.
    • Such as:
      • Every timebuildCreate a new oneGlobalKeyDiscards andThe old KeyAssociated subtree, and isA new KeyCreate a new tree. In addition to hurting performance, this operation may have unknown effects on subtrees.
      • For example, in a subtreeGestureDetectorWill not be able to continue tracking the gesture in progress as it will each timebuildIs recreated
  • It would be better to:
    • To make aStateHolding theGlobalKeyAnd, inBuildInitialize it outside the method
    • For example, inState.initState δΈ­

A simple GlobalKey example

import 'dart:math';

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: GlobalKeyDemo(), ); }}class GlobalKeyDemo extends StatelessWidget {

  final GlobalKey<_GKeyItemState> _gKey = GlobalKey();

  GlobalKeyDemo({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Key's role '),
      ),
      body: Center(
        child: GKeyItem(key: _gKey,),
      ),
      floatingActionButton: FloatingActionButton(
        child: constIcon(Icons.add), onPressed: () { _gKey.currentState? .setState(() { _gKey.currentState? .count +=1; }); },),); }}class GKeyItem extends StatefulWidget {
  const GKeyItem({Key? key}) : super(key: key);

  @override
  _GKeyItemState createState() => _GKeyItemState();
}

class _GKeyItemState extends State<GKeyItem> {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('$count')); }}Copy the code