How to Access the Index in the map or forEach function in Flutter

Flutter Aug 16, 2023

When working with lists in Flutter, you might find yourself needing to access the index of elements as you iterate over them using functions like map and forEach. This can be useful when you want to perform actions on the elements based on their positions within the list. In this post, we will explore various methods to access the index while using the map and forEach functions in Flutter. We will cover using built-in features, extensions, and even the collection package to achieve this functionality.

Before we go over the methods, let us take a look at the list we will be using in the upcoming examples:

List<bool> items = [false, false, false];

Our goal is to change the second item in the list to true by using its index. Therefore all the examples will have the following output in their print statement.

[false, true, false]
While we can easily achieve this using items[1] = true;, consider a situation where we have a long list and want to change values based on certain conditions using their indexes.

Using the asMap function

The asMap function in Dart is a built-in method that transforms a List into a Map. It assigns the current index of each item as the corresponding key. In the examples below, we use this function together with the map and forEach functions to change items in the list based on their index.

void main() {
  List<bool> items = [false, false, false];
  
  List<bool> newItems = items.asMap().entries.map((entry) => entry.key == 1).toList();

  print(newItems);
}

In this code snippet, we call the asMap function on the items list. We then access the entries and map over them using the map function. Inside the map function we can access each entry. Each entry has a key and a value. The key is equal to the index of each item in the list.

When the entry.key is equal to 1 we will return true using entry.key == 1. At last, we have to convert the map back to a list using the toList() function. As you can see with the map function we create a new list.

void main() {
  List<bool> items = [false, false, false];

  items.asMap().entries.forEach(
        (entry) => items[entry.key] = entry.key == 1 ? true : false,
      );

  print(items);
}

The forEach implementation is similar, we also call the asMap function and access the entries. In the forEach function we also have access to each entry and change the second item in our list using items[entry.key] = entry.key == 1 ? true : false). With the forEach function, we modify the existing items list.

Using the indexed getter

Since Dart 3 it is possible to use the indexed getter. The indexed getter enables access to the index when we chain it with the map or forEach function.

void main() {
  List<bool> items = [false, false, false];

  List<bool> newItems =
      items.indexed.map(((int, bool) item) => item.$1 == 1).toList();

  print(newItems);
}

In this code snippet, we call indexed on the list and use the map function, to access the iterable which is of type (int, bool). The int is the index and the bool is the value. We can access the index with item.$1.

void main() {
  List<bool> items = [false, false, false];

  items.indexed.forEach(
    ((int, bool) item) => items[item.$1] = item.$1 == 1 ? true : false,
  );

  print(items);
}

The forEach function can also directly be called on the indexed getter.

Using List.generate

Instead of using the map or forEach function we can also use the List.generate function that enables us to create a new list by applying a function to each index.

void main() {
  List<bool> items = [false, false, false];

  List<bool> newItems = List.generate(items.length, (index) => index == 1);

  print(newItems);
}

In this code snippet, we create a new list using the List.generate function. In the function, we set two parameters: length and generator. We set thelength parameter to items.length to keep the original size. Inside the generator parameter, we have access to the index of each element and we return true or false based on whether the index equals 1 (index == 1).

Using the collection package

The collection package contains utility functions and classes in the style of dart:collection to make working with collections easier. The functions we are interested in from this package are mapIndexed and forEachIndexed.

Installing

First, we need to install the collection package into our project. The installation process is simple. Just execute the following command: flutter pub add collection.

Once the command is executed, make sure to check your pubspec.yaml file for the added dependencies. You should see the collection package included in the dependencies section, like this:

dependencies:
  collection: ^1.17.1
  flutter:
    sdk: flutter

Using mapIndexed

The mapIndexed function can be directly called on theitems list.

import 'package:collection/collection.dart';

void main() {
  List<bool> items = [false, false, false];

  List<bool> newItems = items.mapIndexed((index, item) => index == 1).toList();

  print(newItems);
}

In this code snippet, we call the mapIndexed function on the items list. Inside the function, we have access to the index and item. We return true or false based on the index being 1. We call the toList function to convert the map back into a list.

Using forEachIndexed

The forEachIndexed function can also be directly called on the list.

import 'package:collection/collection.dart';

void main() {
  List<bool> items = [false, false, false];

  items.forEachIndexed((index, item) => items[index] = index == 1 ? true : false);

  print(items);
}

Using the forEachIndexed function we can easily access the index for each item in the list as we did with the mapIndexed function.

So far, these functions represent the simplest ways to achieve our goal. Yet, adding an entire package just for two functions might be a bit much. Therefore, there is one more approach we can explore.

Using extension methods

Instead of using the collection package to use the mapIndexed and forEachIndexed function, we can create them ourselves using extensions.

In short, extensions in Dart are a way to add new functionality to existing classes without modifying their original code. They allow us to define new functions or properties for existing classes, making it easier to organize and extend our code without directly changing the class itself.

Creating IterableMapIndexed extension

The extension keyword introduces an extension named IterableMapIndexed. This extension can be attached to any type that follows the iterable pattern, like lists or sets.

extension IterableMapIndexed<T> on Iterable<T> {
  Iterable<R> mapIndexed<R>(R Function(int index, T element) convert) sync* {
    var index = 0;
    
    for (var element in this) {
      yield convert(index++, element);
    }
  }
}

void main() {
  List<bool> items = [false, false, false];

  List<bool> newItems = items.mapIndexed((index, item) => index == 1).toList();

  print(newItems);
}

Inside this extension, we define the mapIndexed function. This function takes another function called convert as input. The convert function has two parameters: an int that stands for the index and a general type T that represents an element. It returns an outcome of another type R.

The sync* keyword is used to show that the function works as a synchronous generator. This means it can provide multiple values one after another.

We create a variable named index and set it 0.

We then use a for-in to go through each element within the iterable. For each element, we run the convert function. This function gets the current index and the element as inputs. The outcome is passed using the yield keyword, which can give you one value at a time. After that, the index gets increased by one.

Now inside our main function, we can use the mapIndexed function without using the collection package.

Creating IterableForEachIndexed extension

Inside this extension, we create the forEachIndexed function.

extension IterableForEachIndexed<T> on Iterable<T> {
  void forEachIndexed(void Function(int index, T element) action) {
    var index = 0;
	
    this.forEach((element) => action(index++, element));
  }
}

void main() {
  List<bool> items = [false, false, false];

  items.forEachIndexed((index, item) => items[index] = index == 1 ? true : false);

  print(items);
}

The forEachIndexed function accepts another function called action as input. The action function has two parameters: an int that stands for the index and a general type T that stands for an element.

Like the mapIndexed function we create an index variable and set it to 0.

Then we loop over the iterable using a forEach function and call the action function for each element.

Now we can also use the forEachIndexed function without using the collection package.

Conclusion

As you have seen, accessing the index while using the map and forEach functions in Flutter can be achieved in a lot of different ways. By using built-in methods, using external packages, and creating extensions, you can choose an approach that best fits your use case.

Tags