Iterable & Iterator in Flutter & Dart
An in-depth explanation about Iterable & Iterator, Their `methods` & `properties` with Examples.
Iterable
- If you are familiar with Dart/Flutter you are knowingly or unknowingly using Iterable in your application or program.
- You might have come across objects like
List
,Map
, andSet
when building an application. - These are nothing but Iterable. You can verify this by taking a look into the implementation of these collections.
- List :
- Set :
- An Iterable as the name suggests, it has something to do with iteration.
- The definition of Iterable given by Dart is :
An Iterable is a collection of elements that can be accessed sequentially
- If we look at the implementation of the Iterable, we can see that Iterable is an abstract class.
- That means we cannot instantiate the Iterable class.
- But we can create a new Iterable by creating List, Set, Map. For example :
- We cannot access the value by
index
, likenumbers[index]
. It's becauseIterable
doesn't have[ ]
operator like List. - Let's try to access the element of the
Iterable
by[ ]
, And see what error it throws: - As expected it threw an error that says operator [ ] is not defined in Iterable class.
- Iterable has many useful properties and methods available.
- As
List
,Set
is Implementing Iterable , All the methods that are available in Interable are also accessible inList
andSet
. - A
Map
is different thanList
andSet
, because it containskey
:value
pairs. We cannot directly loop through theMap
variable as we're doing inSet
andList
. BecauseMap
is not implementingIterable
- Map has
keys
andvalues
Iterables inside it. - Keys Iterable
- Values Iterable
- So to iterate over the
Map
we can do something like :
Iterator
- In simple terms Iterator is a single element of a collection that we can get one at a time.
- Iterator is an
Interface
. Thefor-in
loop we've used in the above examples is using thisIterator
interface under the hood. - The thing is Iterable doesn't know how to iterate over the collection. That is why Iterable has an
Iterator
getter inside it. - Its job is to move sequentially through all the elements of the iterable.
- All iterables have an iterator
- The Iterator has a method called
moveNext()
, which returns a boolean value. - If it returns
true
then it means that iterable contains the next element. - if it returns
false
then it means that there are no further elements. - Iterator has one very important getter called
current
. Which gives thecurrent element
of the iterable. - Example :
Properties of Iterable :
first
&last
:first
returns the first value/element from theIterable
.last
returns the last value/element from theIterable
.first
Example :void main() { final List<String> listOfCar = ["Audi","BMW","Ferrari","Mercedes"]; print(listOfCar.first); }
- For accessing the
first
element fromMap
: void main() { final Map<String,dynamic> mapOfNumbers = {"0":"Zero","1":"One","2":"Two","3":"Three"}; print(mapOfNumbers.values.first); }
last
Example :final List<String> listOfCar = ["Audi","BMW","Ferrari","Mercedes"]; print(listOfCar.last);
isEmpty
&isNotEmpty
:isEmpty
returnstrue
ifIterable
is empty, otherwisefalse
.isNotEmpty
returnstrue
ifIterable
is not empty, otherwisefalse
void main() { final List<String> listOfCar = ["Audi","BMW","Ferrari","Mercedes"]; print(listOfCar.isEmpty); }
- Same for the
Map
void main() { final Map<String,dynamic> mapOfNumbers = {"0":"Zero","1":"One","2":"Two","3":"Three"}; print(mapOfNumbers.isEmpty); // false }
length
:- Returns the total number of elements/values present in the
Iterable
. void main() { final Map<String,dynamic> mapOfNumbers = {"0":"Zero","1":"One","2":"Two","3":"Three"}; final List<String> listOfCar = ["Audi","BMW","Ferrari","Mercedes"]; print("List: "+listOfCar.length.toString()); print("Map: "+mapOfNumbers.length.toString()); }
runtimeType
:- Returns the type of the object.
void main() { final Map<String,dynamic> mapOfNumbers = {"0":"Zero","1":"One","2":"Two","3":"Three"}; final List<String> listOfCar = ["Audi","BMW","Ferrari","Mercedes"]; print(listOfCar.runtimeType); print(mapOfNumbers.runtimeType); }
single
:- If the Iterable has only one element inside it, then
single
returns that value/element. void main() { final List<String> listOfCar = ["Audi"]; print(listOfCar.single); }
- If there are more than one element then it will throw an error.
Methods of Iterable :
elementAt(index)
:- As we've seen in the explanation of
Iterable
that we cannot access the element ofIterable
by[ ]
. BecauseIterable
doesn't have[ ]
defined. - So we can use
elementAt()
method to access any element from theIterable
. void main() { final Map<String,dynamic> mapOfNumbers = {"0":"Zero","1":"One","2":"Two","3":"Three"}; final Iterable<String> listOfCar = ["Audi","BMW","Ferrari","Mercedes"]; print(listOfCar.elementAt(0)); print(mapOfNumbers.values.elementAt(0)); }
contains(element)
:- Returns
true
if theIterable
contains the element passed inside thecontains()
method. void main() { final Map<String,dynamic> mapOfNumbers = {"0":"Zero","1":"One","2":"Two","3":"Three"}; final List<String> listOfCar = ["Audi","BMW","Ferrari","Mercedes"]; print(listOfCar.contains("BMW")); print(mapOfNumbers.values.contains("Five")); }
firstWhere()
- This will return the first encountered element which satisfies the condition given to the function.
- The function must return
boolean
value. - This method takes two things: 1. The function which returns the value if a certain condition is satisfied & 2.
orElse
function which will be called when no condition is satisfied. void main() { final List<String> listOfCar = ["Audi","Audi A5","BMW","Ferrari","Mercedes"]; print(listOfCar.firstWhere((element)=> element.startsWith("Au"))); }
orElse
void main() { final List<String> listOfCar = ["Audi","Audi A5","BMW","Ferrari","Mercedes"]; print(listOfCar.firstWhere((element)=> element.startsWith("Zu"),orElse: ()=> "No Element Found")); }
where()
- Takes
boolean
function. - This function will iterate over all the elements and checks if the condition given to the function is satisfied on that element.
void main() { final List<String> listOfCar = ["Audi","Audi A5","BMW","Ferrari","Mercedes"]; print(listOfCar.where((element)=> element.length > 4)); }
Map
:void main() { final Map<String,dynamic> mapOfNumbers = {"0":"Zero","1":"One","2":"Two","3":"Three"}; print(mapOfNumbers.values.where((element)=> element.length > 3)); }
toSet()
:- Used to convert from type
T
toSet
, whereT
can be :List
,Map
etc. - The
Set
will remove duplicate element from previous iterable. void main() { final List listOfCar = ["Audi","Audi","BMW","Ferrari","Mercedes"]; print(listOfCar.toSet()); }
toList()
:- Used to convert any iterable to
List
. - Takes one optional argument
growable
. Iftrue
we can furtheradd
elements in list, otherwiseadd()
will not work. void main() { final Set<String> setOfCar = {"Audi","BMW","Ferrari","Mercedes"}; print(setOfCar.toList()); }
growable : true
(default) :void main() { final Set<String> setOfCar = {"Audi","BMW","Ferrari","Mercedes"}; final List listOfCar = setOfCar.toList(); listOfCar.add("Jeep"); print(listOfCar); }
growable : false
:void main() { final Set<String> setOfCar = {"Audi","BMW","Ferrari","Mercedes"}; final List listOfCar = setOfCar.toList(growable: false); listOfCar.add("Jeep"); print(listOfCar); }
- As you can see
add
is not working now.
takeWhile()
:- This will return every element in a list from index 0 to the element that first satisfies the condition.
void main() { final List<String> listOfCar = ["Audi","Audi A5","BMW","Ferrari","Mercedes","Audi A3"]; final fileteredList = listOfCar.takeWhile((element) => element.startsWith("Au")); print(fileteredList); }
take(int count)
:- This will return every element in a list from index 0 to the index specified in this method.
void main() { final List<String> listOfCar = ["Audi","Audi A5","BMW","Ferrari","Mercedes","Audi A3"]; final fileteredList = listOfCar.take(3); print(fileteredList); }
skipWhile()
:- Returns an iterable that skips leading elements while the provided predicate is satisfied.
void main() { final List<String> listOfCar = ["Audi","Ferrari","BMW","Mercedes","Audi A3"]; final fileteredList = listOfCar.skipWhile((element)=> element.endsWith("i")); print(fileteredList); }
skip(int count)
:- This will skip every element in a list from index 0 to the index specified in this method and returns the remaining element.
void main() { final List<String> listOfCar = ["Audi","Ferrari","BMW","Mercedes","Audi A3"]; final fileteredList = listOfCar.skip(3); print(fileteredList); }
singleWhere()
:- Returns the single element that satisfies a given condition.
- If no element is found this method will give an error. To handle this error using
orElse
- This will also throw an error if any duplicate value is found.
void main() { final List<String> listOfCar = ["Audi","Ferrari","BMW","Mercedes"]; final fileteredList = listOfCar.singleWhere((element)=> element.startsWith("Au"),orElse:()=> "No Element Found"); print(fileteredList); }
reduce()
:- This will reduce a given
iterable
to a single value by combining elements of theiterable
using a provided function. - The iterable must have at least one element. If it has only one element, that element is returned.
void main() { final List<int> listNumber = [1,2,3,4]; final reduced = listNumber.reduce((value,element){ print("value: "+value.toString()); print("element "+element.toString()); return value+element; }); print("total: "+reduced.toString()); }
map()
:- Returns a brand new list.
- The function defined in
map
will run on each element. - After that new list will be returned.
void main() { final List<int> listNumber = [1,2,3,4]; final filteredList = listNumber.map((element)=> element += 1); print(filteredList); }
lastWhere()
:- Returns the last element that satisfies the given condition.
void main() { final List<int> listNumber = [1,2,3,4]; final fileteredElement = listNumber.lastWhere((element)=> element.isEven); print(fileteredElement); }
join()
:- Joins every element of the iterable with a given
separator
and returns it as String. void main() { final List<int> listNumber = [1,2,3,4]; final fileteredElement = listNumber.join(","); print(fileteredElement); }
forEach
:- The function defined inside
forEach
will run on every element of the iterable. - The difference between
map
andforEach
isforEach
doesn't return a new list whereasmap
does. void main() { final List<int> listNumber = [1,2,3,4]; listNumber.forEach((element)=> print(element * 2)); }
followedBy()
:- If you want to add new
iterable
inside your currentiterable
then you can usefollowdBy()
by passing youriterable
inside it. void main() { final List listOfFruits = ["Apple","Banana","Carrot"]; final newFruitList = listOfFruits.followedBy(["Dates","Emu"]); print(newFruitList); }
fold
:- It is the same as the
reduce
method. - It reduces an
iterable
to asingle
value by iteratively combining each element of the collection with an existing value. void main() { final List<String> listOfFruits = ["Apple","Banana","Carrot"]; listOfFruits.fold(0, (value, element) { print("value: "+value.toString()); print("element: "+ element.toString()); return value + element.length; }); }
- Difference between
reduce
andfold
is:reduce
can only be used on non-empty collections with functions that return the same type as the types contained in the collection whereasfold
can be used in all cases. - We cannot compute the sum of the length of all strings in a list with
reduce
but you can usefold
to do that.
expand
:- If you have nested
iterables
inside one mainiterable
and you want to extract out those iterables into one single iterable then you can useexpand
to do this. void main() { final nestedList = [["Apple","Banana","Carrot"],[1,2,3],[true,false]]; final flattenedList = nestedList.expand((list)=>list); print(flattenedList); }
void main() { final listOfFruit = ["Apple","Banana","Carrot"]; final duplicateFruits = listOfFruit .expand((element)=>[element,element]); print(duplicateFruits ); }
every()
:- This method returns a boolean depending upon whether all elements satisfies the condition or not.
void main() { final listOfFruits = ["Apple","A Banana","A Carrot"]; final filteredList = listOfFruits.every((element) => element.startsWith("A")); print(filteredList); }
any()
:- Returns
true
if any element satisfies the given condition, otherwisefalse
. void main() { List<int> listOfNumbers = [11,23,3,45,5]; print(listOfNumbers.any((element)=> element.isEven)); }
cast()
:- This will return a new list with a given
castType
so that you can use it in places where the analyzer expectsIterable<Type>
. - This is a very rarely used method.
- Remember this will not cast an element of the
iterable
. - It only gives an illusion to the analyzer that it got that
type
that it wanted. void main() { List<num> listOfNumbers = [5, 20, 12.5]; List<double> castedDoubleList = listOfNumbers.cast<double>(); print("casted list: "+castedDoubleList.toString()); print("casted list type: "+castedDoubleList.runtimeType.toString()); print("casted list element type: "); castedDoubleList.forEach((element)=> print(element.runtimeType)); }
- We can use this
castedDoubleList
if any function requires it. For example: void main() { List<num> listOfNumbers = [5, 20, 12.5]; List<double> castedDoubleList = listOfNumbers.cast<double>(); reverseDoubleList(castedDoubleList); }
reverseDoubleList(List<double> doubleList){ print(doubleList.reversed); }
Wrapping Up
- These all are very useful methods and properties which are very handy during the development.
- Hope you liked it. Thanks for reading.
- Comments and Feedbacks are welcomed ๐
ย