How to Access the Index in the map or forEach function in Flutter
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.