Unlocking Your Flutter Widgets With Keys

Learn how using the right keys in your Flutter widgets can help you avoid UI bugs and improve the performance of your app. By Michael Malak.

Leave a rating/review
Download materials
Save for later
Share

Flutter commonly uses keys when it needs to uniquely identify specific widgets within a collection. Using keys also helps Flutter preserve the state of StatefulWidgets while they’re being replaced with other widgets or just moved in the widget tree. Almost all Flutter widgets accept keys as optional parameters in their constructors.

Have you wondered when to pass a key and what happens under the hood? In this tutorial, you’ll unlock that mystery as you build a simple app to manage a TODO list and display news headlines.

By the end of this tutorial, you’ll learn:

  • What keys are and how they work.
  • When to use a key.
  • How to work with different types of keys.
Note: This tutorial assumes that you have some experience with Flutter and Flutter widgets. If you don’t, check out our Getting Started with Flutter tutorial, our Flutter UI Widgets video course or our Flutter Apprentice book.

Getting Started

Download the starter project by clicking the Download Materials button at the top or bottom of the tutorial.

This tutorial uses Android Studio 4.1. Some of the screenshots are specific to it, but you can follow along with Visual Studio Code or IntelliJ as well.

You’ll work on The Morning App, a single-page app that displays a TODO list on one tab and a list of news articles on another. Here’s what you want to be able do with each page:

  • Todos: Add a new TODO, mark a TODO as done or delete a TODO.
  • News: View the latest news articles from HackerNews and tap an article to view some of its metadata.

Here’s how the pages will look when you’re done:

Todos tab in TheMorningApp Starter
News tab in TheMorningApp Starter

Now, it’s time to take a look at the project.

Setting up the Starter Project

The starter project already contains the logic to fetch articles from HackerNews, save TODOs to the cache and read TODOs from the cache.

Open Android Studio and choose Open an Existing Project. Then, choose the starter folder from the downloaded materials.

Choose Open an Existing Project

Fetch the dependencies declared in pubspec.yaml by clicking Pub get at the top of the pane when you’re in this file.

Fetch dependencies declared in pubspec.yaml file

For this tutorial, the most important files in the project are:

  1. lib/ui/home/home_page.dart: The main page of the app that displays the two tabs for displaying TODOs and news articles.
  2. lib/ui/todos/todos_page.dart: The widget class of the page associated with the Todos tab.
  3. lib/ui/news/news_page.dart: The widget class of the page associated with the News tab.
  4. lib/ui/todos/add_todo_widget.dart: The widget class representing the bottom sheet, where the user can add a new TODO on the Todos page.

Build and run. The app launches with the Todos tab selected.

Todos tab in TheMorningApp Starter

Now that you know what the starter project contains, you’ll take a deeper look at what keys are and why you use them.

Understanding Keys

Every Flutter widget can have a key, but adding them isn’t always useful. Here’s the key to understanding keys:

  1. Multiple widgets of the same type and at the same level in a widget tree may not update as expected unless they have unique keys, given that these widgets hold some state.
  2. Explicitly setting a key to a widget helps Flutter understand which widget it needs to update when state changes.
  3. Among other things, keys also store and restore the current scroll position in a list of widgets.

Consider an example to understand this better:

When Flutter lays out the widget tree, it builds a corresponding element tree. Internally, it maps each widget in the widget tree to an element in the element tree. The widget tree contains information about the UI and the element tree holds information about the structure of the app — meaning that each element holds details about:

  • The runtimeType of the corresponding widget in the widget tree.
  • The reference to the corresponding widget in the widget tree.
  • The reference to its child Element.

You can extract the rest of the information from the reference to the widget tree that each element holds.

Widget Tree and Element Tree

Making Swaps With Stateless Widgets

Every change to the UI in a Flutter app is a result of triggering the build method. During this process, Flutter checks if the element tree is the same as the corresponding widget tree. Flutter makes this comparison starting from the parent widget, then proceeding to its children widgets.

StatelessWidgets have no keys. Therefore, if the element has the same type as the corresponding new widget, the element updates its reference to point to the new widget and drops the reference to the old widget.

Swapping Stateful Widgets without keys

Handling Swaps in Stateful Widgets

In the case of StatefulWidgets, however, an element stores a reference to the state of a widget — for example, State — as well.

Therefore, the new widget could have the same runtime type as the old widget, but a different state. Based on the logic above, Flutter would update the reference of the widget in the element to point to the new widget but the element would still hold a reference to the state from the old widget. That’s a problem.

Using Keys to Avoid Unexpected Results

Adding a key to a widget that holds a reference to the state allows Flutter to make an additional comparison beyond the type of the widget. This ensures that when the types match but the keys don’t, Flutter forces the elements to drop to their widget reference and hold references to widgets where both the type and key match. This ensures that both the widget and state references update correctly.

Swapping stateful widgets with keys

You’ll see this in action later in the tutorial.

Now that you understand some theory behind using keys, it’s time to put that information to work by adding some new features to The Morning App.

Reordering TODOs

At this point, the starter code displays the TODOs. Your first goal is to give the users the ability to sort the TODO items by dragging and dropping them to new positions in the list.

In lib/ui/todos/todos_page.dart, replace //TODO: Reorder To-dos with:

// 1
void reorderTodos(int oldIndex, int newIndex) {
  // 2
  if (oldIndex < newIndex) {
    newIndex -= 1;
  }
  
  // 3
  final item = todos.removeAt(oldIndex);
  setState(() {
    todos.insert(newIndex, item);
  });
}

Here's what you did:

  • You added a function to reorder the TODOs. That function takes two indices as parameters: oldIndex is the index of the TODO whose position will change and newIndex is the new index where you'll place the TODO.
  • Since you're going to remove the TODO from the old index then insert it into the new one, you subtracted 1 from newIndex in case it's after oldIndex.
  • You removed the element at oldIndex and inserted it into newIndex. You then called setState so the UI reflects the changes.

Now that you've made the TODO items sortable, it's time to add the ability to drag and drop them.