What are Keys in Flutter?

What are Keys in Flutter?

Working with Flutter, many times we bump into something called keys. The key is a property possessed by almost all widgets in Flutter. However, it is not used a lot and therefore is often overlooked. Why do widgets have keys then? Is it of any significance to us? Let's find out.

What are keys?

Flutter describes Keys as an identifier for Widget, Element, and SemanticNodes. But what does that mean? This simply means that keys are unique labels assigned to widgets so that they can be distinguished from other widgets. For cases when widgets change positions in the widget tree, keys essentially help preserve their states. This also means that keys mostly come in handy for stateful widgets rather than stateless ones.

When to use them?

Keys can go almost everywhere in our code and they do not cause any harm. But placing keys when it isn't required only means that it is unnecessarily hogging up memory spaces. Therefore, one must be clear on when to use them as well.

Most cases do not require the need to work with keys. They are useful while adding, removing, or reordering a collection of widgets of the same type that hold some state and are at the same level in the widget tree. In the absence of keys, updating such a collection of widgets may not give the expected kind of results. We tend to use keys with children of widgets like ListViews or Stateful widgets whose data is constantly changing.

To further clarify, why we need keys when modifying a collection of widgets here is a simple example illustration. The example described below shows two color tiles that swap places at the click of a button.

swapping tiles

Figure 1: Swapping tiles example

The example is represented in two ways.

Firstly, the color tiles widgets are stateless with the color property stored in the widget itself. When the FloatingActionButton is tapped the tiles properly swap their position as expected.

import 'package:flutter/material.dart';
import 'package:keys_example/value_key_example.dart';
import 'package:random_color/random_color.dart';

void main() {
  runApp(const MyApp());
}
class MyApp extends StatelessWidget {
  const MyApp({super.key});
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home:const PositionTiles(),
    );
  }
}
class PositionTiles extends StatefulWidget {
  const PositionTiles({Key? key}) : super(key: key);
  @override
  State<PositionTiles> createState() => _PositionTilesState();
}
class _PositionTilesState extends State<PositionTiles> {
  List<Widget> tiles = [];
  @override
  void initState() {
    super.initState();
    tiles = [ 
       StatelessColorTiles(),
        StatelessColorTiles(),
    ];
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(child: Row(mainAxisAlignment:MainAxisAlignment.center,children: tiles,),),
      floatingActionButton: FloatingActionButton(child: Icon(Icons.sentiment_very_satisfied, ),onPressed: swapTiles,),
    );
  }
  void swapTiles() {
    setState(() {
      tiles.insert(1, tiles.removeAt(0));
    });
  }
}
class StatelessColorTiles extends StatelessWidget {

 Color myColor = RandomColor().randomColor();
  @override
  Widget build(BuildContext context) {
    return  Container(
      height: 100,
      width: 100,
      color: myColor,
    );
  }
}

Secondly, we make the color tile widgets stateful and store the color property in the state. This time, when pressing the floating action button nothing seems to happen. For the tiles to swap places correctly we need to add a key parameter to the stateful widgets.

import 'package:flutter/material.dart';
import 'package:keys_example/value_key_example.dart';
import 'package:random_color/random_color.dart';

void main() {
  runApp(const MyApp());
}
class MyApp extends StatelessWidget {
  const MyApp({super.key});
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home:const PositionTiles(),
    );
  }
}
class PositionTiles extends StatefulWidget {
  const PositionTiles({Key? key}) : super(key: key);
  @override
  State<PositionTiles> createState() => _PositionTilesState();
}
class _PositionTilesState extends State<PositionTiles> {
  List<Widget> tiles = [];
  @override
  void initState() {
    super.initState();
    tiles = [ 
        StatefulColorTiles(key: UniqueKey(),),
        StatefulColorTiles(key: UniqueKey(),),

    ];
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(child: Row(mainAxisAlignment:MainAxisAlignment.center,children: tiles,),),
      floatingActionButton: FloatingActionButton(child: Icon(Icons.sentiment_very_satisfied, ),onPressed: swapTiles,),
    );
  }
  void swapTiles() {
    setState(() {
      tiles.insert(1, tiles.removeAt(0));
    });
  }
}
class StatefulColorTiles extends StatefulWidget {
  const StatefulColorTiles({Key? key}) : super(key: key);
  @override
  State<StatefulColorTiles> createState() => _StatefulColorTilesState();
}
class _StatefulColorTilesState extends State<StatefulColorTiles> {
  late Color myColor;
  @override
  void initState() {
    super.initState();
    RandomColor _randomColor = RandomColor();
    myColor = _randomColor.randomColor();
  }
  @override
  Widget build(BuildContext context) {
    return Container(
        height: 100,
        width: 100,
        color: myColor,
     );
  }
}

The example thus, shows that keys are required for modifying widgets only if they are stateful. Stateless widgets don't require them.

Behind the scenes:

We just looked at an example where using keys allowed us to achieve the expected behavior from our code. But why did keys make this possible? Let's find out.

When rendering widgets, flutter not only builds a widget tree but side by side its corresponding element tree as well. The element tree holds information about the widgets present in the widget tree and references to its children’s widgets. In the process of modifying and re-rendering, flutter looks up the element tree to see whether or not the element tree has changed, so that if in case the element hasn't been changed it can reuse the old one itself.

For the stateless example, each of the tile widgets has its corresponding tile element. Because the color property was saved in the widget itself, when swapping the tiles in the widget tree, the references in the element tree weren't affected as they were the tile element itself. This thereby accurately swapped the color tiles.

For the stateful example, each tile widget had its corresponding tile element along with an extra state property in the element tree. When swapping the tiles, the corresponding elements do not match because they hold the state property and the desired behavior isn't achieved. After adding the key parameter to the tiles, the element tree and the widget tree get updated with the key values. Now when we swap the tiles, the tile elements with the help of their key can find out their corresponding widgets in the widget tree and update their reference correctly resulting in the widgets correctly swapping places and updating their color when the button is pressed.

Hence, this is how keys work under the hood and are useful for modifying stateful widgets in a collection.

Types of Keys:

Keys are broadly classified into two types:

  1. Local Keys

  2. Global Keys

Local Keys :

These must be unique amongst the elements with the same parent. Local keys can further be classified as follows:

Value Key:

Value key takes alphanumeric values. They are commonly used in a list of children where the value of each child is unique and constant.

Figure 2: Value Key example

Object Key:

Same as the value key with the only difference being that it takes in a class object that holds data.

Figure 2: Object Key example

Unique Key:

A unique key is used to identify a widget’s child in cases where they do not have unique values or don't have values at all.

Figure 2:Unique Key example

Page Storage Key:

This key is used to preserve the scroll location of a user in scrolling views so that it can be saved for later.

That is all for this article. Stay tuned to learn about global keys in my next blog.

Till then, Happy reading!!