Dart: Iterables
An iterable is a collection of items that you are able to "step" or iterate through sequentially. In Dart, Iterable is an abstract class which exposes an API for retrieving items.
Both List
and Set
collection classes are predictable in the way they return items. This is because they
store each item in a specific position. For Map
collections, both the keys
and values
are iterable. The default
internal implementation of Map
uses LinkedHashMap
, which iterates the keys in the order they were inserted.
Getting an iterator
You can directly create an Iterable
using the default growable list shorthand:
Iterable<String> characters = <String>[
'Cloud Strife',
'Aerith Gainsborough',
'Barrett Wallace',
'Red XIII',
'President Shinra',
'Sephiroth'
];
This will give you a plain Iterable
with no additional efficient item access methods.
💡 Tip
You should always use the most specific collection type that you require for your needs. If you only need tofor...in
loop over a collection, useIterable
. If you need to access items in the collection via an index, useList
.
To get an iterator for a collection you access the iterator
property:
Iterator<String> iterator = characters.iterator;
Test predicate
You'll see a few examples coming up that use a test predicate. This is a function that returns
a boolean value, very much like the expression in an if ()
statement.
These predicates are used to:
- Determine whether the iterable:
- contains any item/s. (
any
) - contains specific item/s (
contains
) - has all items that match specific conditions (
every
)
- contains any item/s. (
- Retrieve:
- all items where they satisfy the test (
where
) - the first item that satisfies the test (
firstWhere
) - a single item that satisfies the test (
singleWhere
)
- all items where they satisfy the test (
I won't go into too much detail about some of these methods, so just keep in mind that a predicate is a test that specifies conditions that pass or fail for items in the iterable.
Movement
The iterator is like a shuttle in a hand loom, it moves along the collection's items, replacing its current
property with the value it finds at each step.
In order to "move" the iterator to the next item, you use the moveNext()
method:
iterator.moveNext();
If this method returns true
, it has successfully been able to move to the next element. If there are no more
items moveNext()
will return false
, this indicates that it cannot move any further.
It is safe to repeatedly call moveNext()
even when there are no items, it will just keep returning false
.
Initial value
The initial value of an iterator's current
property is null
. This is because the iterator starts positioned
before the first item in the collection.
Iterator<String> iterator = characters.iterator;
print(iterator.current); // null
Performing checks
There are various properties and methods on an Iterable
you can use in order to determine its current state:
// Returns the total number of items in the iterable
characters.length; // 6
// Checks if the iterable is empty
characters.isEmpty; // false
// Convenience method for checking the opposite of isEmpty
characters.isNotEmpty; // true
// Checks if any items in the iterable satisfy the test predicate
characters.any((item) => item == 'Red XIII')); // true
// Returns true if the iterable contains the item provided
characters.contains('Barrett Wallace'); // true
characters.contains('Cait Sith'); // false
// Checks if the iterable has a single item in it and returns
// that item. If it contains more than one item a StateError
// will be thrown.
characters.single; // StateError
Getting items
You can easily get the first and last items in an Iterable
using first
or last
:
print(characters.first);
print(characters.last);
Cloud Strife
Sephiroth
🚧 Warning
Usinglast
can be very slow since it has to iterate over all items to find the last one. Some iterables, such asList
, have efficient ways of finding the last item. Make sure you understand the performance implications for a particular collection type and choose appropriately.
Single items
If you try to access an item at a specific index using []
the compiler will complain that this operator
is not defined for Iterable
s. This is because the Iterable
cannot guarantee that accessing an item by
an index is efficient.
Instead, you can use elementAt()
:
print(iterable.elementAt(1));
Aerith Gainsborough
This makes the Iterable
move to the given index (1
) and return the item it has. Index
0
represents the first item, so iterable.elementAt(0)
is equivalent to iterable.first
.
Loops
You may not realise it, but a for...in
loop actually uses an iterator under the hood, the language just
hides away this complexity:
for (String name in characters) {
print(name);
}
Iterables
also define a forEach()
method which loops over each item in the Iterable
just like for...in
,
but is more "functional":
characters.forEach((String character) {
print(character);
})
Since moveNext()
returns a boolean, this can be used as the control statement for a while
loop:
Iterator<String> iterator = characters.iterator;
while(iterator.moveNext()) {
print(iterator.current);
}
This will continually execute until there are no more items left.
Filtering items
Finding items in an Iterable
is simple and there are several methods in Dart that make life easy.
Where
Where iterates over the list and returns items that satisfy the test predicate:
Iterable<String> filteredCharacters =
characters.where((item) => item.contains('a'));
Aerith Gainsborough
Barrett Wallace
President Shinra
Skip
You can skip items using the skip()
method. This returns an Iterable
containing all but the first x number of items:
characters.skip(2);
Barrett Wallace
Red XIII
President Shinra
Sephiroth
Take
This takes x number of items from the Iterable
and returns them:
Iterable<String> smallerCharacterList = characters.take(2);
Cloud Strife
Aerith Gainsborough
The take()
method returns a lazy iterable and the function provided will not execute until
characterNameLengths
itself is iterated over.
Mapping items
The map()
method allows you to iterate over each item in the Iterable
, executing a function for each one:
Iterable<int> characterNameLengths =
characters.map((item) => item.length);
Just like take()
, map()
returns a lazy Iterable
.
Notice we are mapping to a completely different data type (Iterable<int>
), transformation is just one of the
things you can do with it. You will see map()
used later in examples when creating Widget
instances
for dynamic UIs.
Converting to different collection types
To List
To create a list from your Iterable
, you can use the toList()
method:
characters.toList();
You can also pass a boolean value to toList()
in order to create a growable list:
List<String> listOfCharacters = characters.toList(true);
To Set
To create a Set
from your Iterable
, you can use the toSet()
method:
Set<String> setOfCharacters = characters.toSet();
The set may contain fewer items because duplicate items will be discarded. The order
of the items in the set is not guaranteed to be the same as the Iterable
.
There are a lot of other methods I've not gone into detail about here, these are just the essentials to get you working with iterables.