Ways to work with lists in Dart

widget

A very commonly used collection in programming is an array. Dart represents arrays in the form of list objects. A list is simply an ordered group of objects. The dart:core library provides the List class that enables the creation and manipulation of lists.

Lists are iterables

An iterable provides a means to traverse a collection of items in a sequential fashion. Dart takes an approach similar to most other programming languages. Basically, you have what is known as an iterator that keeps track of the current item in a list and allows you to move forward through the list using the next method. More on Dart’s Iterator can be found on the official website.

In practice, you will find yourself almost always working with some sort of abstraction of the iterator, not the iterator itself. There are multiple classes in the Dart library that you can iterate, but you will work with some more than others. The List is a workhorse and you’ll find it to be your go-to more often than not. However, it’s certainly worth knowing about others such as Set, Map, and Queue.

Here is what we will cover in this article:

  • Creating lists
  • Looping
  • Filtering
  • Mapping
  • Sorting

Creating lists

There are many different ways to create a list. Sometimes you’ll know the length of your list and sometimes you won’t. Both approaches are easily accommodated by Dart. There are even means to dynamically generate a list. Here are some common examples of how to create a list of strings. I chose strings but, as you can imagine, any type would work:

void main() {
//Fix lengthed list
var namesFixed = new List(3);
namesFixed[0] = "Joe";
namesFixed[1] = "Frankie";
namesFixed[2] = "Tom";
//namesFixed[3] = "Kaboom!"; //this would be an error
log(namesFixed) ;
//Growable list
var namesGrowable = new List();
namesGrowable.add("Joe");
namesGrowable.add("Frankie");
namesGrowable.add("Tom");
namesGrowable.add("Larry");
log(namesGrowable) ;
//List initialized upfront
List names = ["Joe", "Frankie", "Tom"];
log(names);
names.add("Dave"); //Can add just fine, since this one is growable too!
log(names);
//Using filled to set placeholder values on a fixed length list of 3
var namesFilled = new List.filled(3, "placeholder");
log(namesFilled); //prints placeholder 3 times...
namesFilled[0] = "Joe"; //Update first entry
namesFilled[1] = "Frankie"; //Update second entry
log(namesFilled);
//Using generate (useful to create a long list of something like test data or to call a generator function)
List namesGenerated = new List.generate(3, (i) => "Name ${i}");
log(namesGenerated);
}
void log(List lst) {
lst.forEach((n) => print(n));
print('=========');
}

Looping

Looping through a list is very common. For example:

void main() {
List names = ["Joe", "Frankie", "Tom"];
for (var n in names) {
print('Hello ${n}');
}
}

What the above code does is establish a list of strings that contain names. We then create a for loop and print the name out to the console. Here is another way to iterate that list of names:

void main() {
List names = ["Joe", "Frankie", "Tom"];
names.forEach((n) => print('Hello Mr. ${n}'));
}

I like the forEach approach with a fat arrow (=>) function, but the examples essentially do the same thing.

Filtering

Filtering comes into play from the where method:

void main() {
List prices = [0.25, 1.00, 3.33, 0.75, 4.25, 5.99];
var overOneDollar = prices.where((p) => p > 1.00);
log(overOneDollar); //prints 3.33, 4.25, 5.99
}
void log(var lst) {
lst.forEach((n) => print(n));
}

Essentially, what is happening in the above code is that each price is examined in the fat arrow function and, if it is greater than one dollar, it passes the condition and is added to the overOneDollar result.

Mapping

Mapping allows you to take a list and transform it into something else, a’la the map method. For example:

class Person
{
String firstName;
String lastName;
Person(this.firstName, this.lastName);
}
void main() {
List people = new List();
people.add(new Person("Joe", "Smithers"));
people.add(new Person("Patrick", "Thomas"));
var mappedNames = people.map((n) => 'Mr. ${n.firstName} ${n.lastName}');
log(mappedNames);
}
void log(var lst) {
lst.forEach((n) => print(n));
}

Sorting

The sort method allows you to sort. Default types like numbers and strings don’t need much to get started:

void main() {
List prices = [0.25, 1.00, 3.33, 0.75, 4.25, 5.99];
prices.sort();
log(prices); //prints 0.25, 0.75, 1, 3.33, 4.25, 5.99
List alphabet = ["b", "c", "a"];
alphabet.sort();
log(alphabet); //prints a,b,c
}
void log(var lst) {
lst.forEach((n) => print(n));
}

Sorting numbers and strings is straight-forward. But what if you had a list of objects to sort? Here is an example that takes a list of Employee objects and ranks them by sales. The employee with the most sales is displayed on top:

class Sale {
int employeeId;
double price;
Sale(this.employeeId, this.price);
}
class Employee {
int id;
List sales;
Employee (this.id, this.sales);
}
void main() {
//Create a list of employees and their respective sales
List employees = new List();
employees.add(new Employee(1, [new Sale(1, 100.50), new Sale(1, 300.25)]));
employees.add(new Employee(2, [new Sale(2, 300.00), new Sale(2, 50.25), new Sale(2, 150.00)]));
employees.add(new Employee(3, [new Sale(2, 400.00), new Sale(2, 30.75), new Sale(3, 50.00)]));
//Sort so that the employee with the most sales is on top and so on...
employees.sort((a, b) => (b.sales.fold(0, (prev, element) => prev + element.price)).compareTo(a.sales.fold(0, (prev, element) => prev + element.price)));
log(employees); //prints Employee #2, followed by Employee #3, then ending with Employee #1
}
void log(var lst) {
lst.forEach((l) => print("Employee #${l.id} has ${l.sales.length} sales totaling ${l.sales.fold(0, (prev, element) => prev + element.price)} dollars!"));
}

This should give you a good start on working with lists, but you should explore the other types of collections Dart has as well.

Attributions:
  1. undefined by undefined