How to make Flutter keys and use cases

5 min read
zen8labs Flutter Keys and Use Cases
Flutter keys and use cases

Introduction 

Flutter is an open source framework for multi-platform applications. In Flutter, keys are often overlooked until you encounter strange issues with data rendering. Keys are objects used to distinguish widgets in the widget hierarchy, allowing Flutter to update and manage the user interface efficiently. Let me give you some guidance to the types of keys used in Flutter to help you succeed with this software

Types of keys 

Value Key 

Value key’s are commonly used in a list of children with the condition that the values of the child elements are unique and constant. 

List<String> items = ['Apple','Banana','Orange']; 
ListView.builder( 
itemCount: items.length, 
itemBuilder: (context, index) { 
return ListTile( 
            // Using ValueKey with item value 
            key: ValueKey(items[index]),  
            title: Text(items[index]), 
       ); 
    }, 
);

Object Key 

Similar to the ValueKey, the ObjectKey takes its identity from the object used as its value. Additionally, it is often used with class objects that holds data. 

List<String> items = List.generate(10, (index) => 'Item $index'); 

  @override 

  Widget build(BuildContext context) { 

    return ListView.builder( 

      itemCount: items.length, 

      itemBuilder: (context, index) { 

        return MyListItem( 

          key: ObjectKey(items[index]), // Using ObjectKey with the item's value 

          item: items[index], 

        ); 

      }, 

    ); 

  } 

}

Note: 

The difference between ValueKey and ObjectKey lies in their comparison mechanisms. ValueKey uses the operator for comparison, whilst the ObjectKey checks if its internal object value is as identical (referring to the same instance) as the other.

This distinction is the reason why ObjectKey is recommended for use with class objects. When dealing with complex class instances, it is often more appropriate to use ObjectKey because it relies on object identity rather than the equality operator. This ensures that two different instances of the same data are not considered equal unless they are the same instance, which can be crucial in scenarios where maintaining identity is essential.

Unique Key 

“A key that is only equal to itself.” 

It cannot be created with a const constructor because doing so would generate keys with a similar identity, thus making them no longer unique. 

In the situation where the children do not have unique values or don’t have a value at all, the Uniquekey is used to identify each child. 

Widget build(BuildContext context) { 
return Container( 

      // Using UniqueKey to force recreation of the widget 
      key: UniqueKey(),   

      child: // … 
  ); 
}

Global Key 

Unlike a ValueKey, ObjectKey, and UniqueKey that are subclasses of LocalKey, this makes a widget unique throughout the entire application rather than a specific area. It is often used to invoke a method in which a child widget or access the state of a specific widget from anywhere in the program. 

It does have a few good and valid use cases such as Form validation. When assigned to a Form, one can access the FormStates public functions and variables and use them accordingly. 

final formKey = GlobalKey<FormState>(); 

         Form( 

           //assign the globalkey to the key value of the form widget 

            key: formKey, 

            child: ListView( 

              children: [ 

                  //...     

                  TextButton( 

                    onPressed: () { 

                      //access the Form widgets variables and functions 

                      //in this case we use the validate() method 

                      if (!formKey.currentState!.validate()) { 

                        print('error in the form'); 

                        return; 

                      } 

                    }, 

                    child: Text('Validate')) 

              ], 

            ), 

          ),

Use cases of Keys in Flutter 

Widget Identity and Preservation 

Keys help differentiate between different instances of similar widgets. When a widget is rebuilt, a new instance of the widget is created, but using keys allows Flutter to recognize that the widgets are similar and preserve their state. This is particularly useful when working with “StatefulWidget”

ListView.builder( 

        itemCount: items.length, 

        itemBuilder: (context, index) { 

          return Dismissible( 

            key: Key(items[index]), // Using Key to identify each item 

            onDismissed: (direction) { 

              removeItem(index); 

            }, 

            background: Container( 

              color: Colors.red, 

              child: Icon(Icons.delete, color: Colors.white), 

            ), 

            child: ListTile( 

              title: Text(items[index]), 

            ), 

          ); 

        }, 

      )

In this example, each “Dismissible” widget is assigned a “Key” using the value of the corresponding item. This ensures that each item in the list is uniquely identified. 

Efficient Widget Reordering 

When dealing with lists of widgets, keys help Flutter efficiently rearrange the widgets without having to rebuild the entire list. 

By providing a key for each item in the list, Flutter can identify and move widgets to their new positions without unnecessary widget reconstruction. 

List<String> items = List.generate(5, (index) => 'Item $index'); 

ReorderableListView( 

      onReorder: (oldIndex, newIndex) { 

        setState(() { 

          if (oldIndex < newIndex) { 

            newIndex -= 1; 

          } 

          final item = items.removeAt(oldIndex); 

          items.insert(newIndex, item); 

        }); 

      }, 

      children: items 

          .map((item) => ListTile( 

                key: Key(item), // Using Key to identify each item 

                title: Text(item), 

                leading: Icon(Icons.drag_handle), 

              )) 

          .toList(), 

    );

In this example: 

  • The ReorderableListView widget is used to create a list that allows items to be reordered by dragging. 
  • Each ListTile has a unique Key assigned to it using the value of the corresponding item. 
  • The onReorder callback is triggered when an item is reordered. It updates the state by swapping the positions of the items in the list. 
  • The Key ensures that Flutter can efficiently update the widget tree when the order of items changes, preserving the state of each item. 

Preserving state in animated widgets 

When you work with animated widgets such as “Animatedlist, Animatedbuilder, or AnimatedContainer“, it is really important to ensure them operate. In this case, Keys allow Flutter to maintain the state during animations.

Without keys, animations might behave unexpectedly or reset their progress during rebuilds. 

final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>(); 

List<String> items = ['Item 1', 'Item 2', 'Item 3']; 

  @override 

  Widget build(BuildContext context) { 

    return Column( 

      children: [ 

        ElevatedButton( 

          onPressed: () { 

            addItem(); 

          }, 

          child: Text('Add Item'), 

        ), 

        AnimatedList( 

          key: _listKey, 

          initialItemCount: items.length, 

          itemBuilder: (context, index, animation) { 

            return buildAnimatedListItem(items[index], animation); 

          }, 

        ), 

      ], 

    ); 

  } 

  Widget buildAnimatedListItem(String item, Animation<double> animation) { 

    return FadeTransition( 

      opacity: animation, 

      child: ListTile( 

        title: Text(item), 

        // Add other widget properties or behaviors as needed 

      ), 

    ); 

  } 

  void addItem() { 

    setState(() { 

      final newIndex = items.length; 

      items.add('Item $newIndex'); 

      _listKey.currentState?.insertItem(newIndex); 

    }); 

  } 

}

In this example: 

  • An AnimatedList is used with a GlobalKey to manage the state of the animated list. 
  • Each item in the list is represented by a FadeTransition to achieve a fade-in effect when added and a fade-out effect when removed. 
  • The addItem function adds a new item to the list and triggers the animation for the new addition. 
  • The animations are handled automatically through the AnimatedListState

Conclusion 

In conclusion, keys are essential in Dart and Flutter for managing widget identity, preserving state, and optimizing performance. They offer significant advantages in managing widget trees and maintaining widget state during rebuilds, reordering, and animations. However, using keys requires careful consideration to ensure uniqueness and proper integration with the app’s architecture. When used correctly, keys play a crucial role in building efficient and responsive Flutter applications. For more IT insights and hints, check out here!

Thanh Nguyễn, Mobile Engineer

Related posts

In Go, slices are one of the most important data structures, providing developers with a way to work with and manage collections of data similar to what we use at zen8labs. In this blog, I will introduce some internal aspects of slices and highlight some pitfalls to avoid when using slices in Go.
5 min read
One of the key strengths of Odoo is its modular architecture, which allows developers to extend and modify existing modules to their needs. In zen8labs' latest blog, we look at some ways that you can use Odoo to your prosperity.
3 min read
For beginners exploring Redux, you'll come across many tutorials using Redux Thunk or Redux Saga to manage async actions. However, here at zen8labs we can give recommendations between using Redux Saga over Redux Thunk in large-scale projects. Read them in our latest blog.
4 min read