Chapters

Hide chapters

Dart Apprentice: Fundamentals

First Edition · Flutter · Dart 2.18 · VS Code 1.71

Dart Apprentice: Fundamentals

Section 1: 16 chapters
Show chapters Hide chapters

12. Lists
Written by Jonathan Sande

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

Need to go shopping? Make a shopping list so you don’t forget what to buy. Have a bunch of tasks to finish? Make a to-do list. Lists are a part of everyday life. They’re also an important part of programming.

In almost every application you make, you’ll deal with data collections. List is the primary collection type you’ll work with in Dart. A list is ideal for storing many objects of the same type in an ordered way. Lists in Dart are like what other languages call arrays.

The image below represents a list with six elements. Lists are zero-based, so the first element is at index 0. The value of the first element is “cake”, the value of the second element is “pie” and so on until the last element at index 5, which is “cookie”.

The order of a list matters. Pie comes after cake but before donut. If you loop through the list multiple times, you can be sure the elements will stay in the same location and order.

Basic List Operations

Like a good dessert, Dart lists have a lot of goodness baked right in. In the next few sections, you’ll learn how to create lists, modify them and access their elements.

Creating a List

You can create a list by specifying its initial elements in square brackets. This is called a list literal.

var desserts = ['cookies', 'cupcakes', 'donuts', 'pie'];
desserts = [];
var snacks = [];
List<String> snacks = [];
var snacks = <String>[];

Printing Lists

As you can do with any collection, you can print the contents of a list with a print statement. Because desserts is currently empty, give it a list with elements again so you have something interesting to show when you print it:

desserts = ['cookies', 'cupcakes', 'donuts', 'pie'];
print(desserts);
[cookies, cupcakes, donuts, pie]

Accessing Elements

To access a list’s elements, you reference its index via subscript notation, where the index number goes in square brackets after the list name.

final secondElement = desserts[1];
print(secondElement);
final index = desserts.indexOf('pie');
final value = desserts[index];
print('The value at index $index is $value.');
The value at index 3 is pie.

Assigning Values to List Elements

You can change the value of a list element the same way you access its value — that is, by using subscript notation:

desserts[1] = 'cake';
print(desserts);
[cookies, cake, donuts, pie]

Adding Elements to the End of a List

Lists are expandable by default in Dart, so you can use the add method to add an element.

desserts.add('brownies');
print(desserts);
[cookies, cake, donuts, pie, brownies]

Inserting Elements

Sometimes you want to add an element somewhere besides the end of the list. You can accomplish this with the insert method.

desserts.insert(1, 'ice cream');
print(desserts);
[cookies, ice cream, cake, donuts, pie, brownies]

Removing Elements

You can remove elements from a list using the remove method. So if you’d gotten a little hungry and eaten the cake, you’d write:

desserts.remove('cake');
print(desserts);
[cookies, ice cream, donuts, pie, brownies]
desserts.removeAt(0);
print(desserts);
[ice cream, donuts, pie, brownies]

Sorting Lists

Having order in your life makes things easier. The same is often true for lists. For example, it’s easier to tell what the largest or smallest numbers are if you sort the list first. The easiest way to sort a list in Dart is through the sort method.

final integers = [32, 73, 2, 343, 7, 10, 1];
integers.sort();
print(integers);
[1, 2, 7, 10, 32, 73, 343]
final smallest = integers[0];
print(smallest); // 1

final lastIndex = integers.length - 1;
final largest = integers[lastIndex];
print(largest); // 343
final animals = ['zebra', 'dog', 'alligator', 'cat'];
animals.sort();
print(animals);
[alligator, cat, dog, zebra]

Exercise

  1. Create a list of type String and name it months.
  2. Use the add method to add the names of the twelve months.
  3. Find the index of March in the list.
  4. Use the index to remove March.
  5. Insert March back in at the correct position.
  6. Print the list after each change to ensure your code is correct.

Mutable and Immutable Lists

In the examples above, you were able to reassign list literals to desserts like so:

var desserts = ['cookies', 'cupcakes', 'donuts', 'pie'];
desserts = [];
desserts = ['cookies', 'cupcakes', 'donuts', 'pie'];
final desserts = ['cookies', 'cupcakes', 'donuts', 'pie'];
desserts = [];          // not allowed
desserts = ['cake', 'ice cream']; // not allowed
desserts = someOtherList;     // not allowed
final desserts = ['cookies', 'cupcakes', 'donuts', 'pie'];
desserts.remove('cookies');  // OK
desserts.remove('cupcakes'); // OK
desserts.add('ice cream');  // OK

The House on Wenderlich Way

You live in a house at 321 Lonely Lane. All you have at home are a few brownies, which you munch on as you scour the internet in hopes of finding work. Finally, you get a job as a junior Flutter developer, so you buy a new house at 122 Wenderlich Way. Best of all, your neighbor Ray brings over some cookies, cupcakes, donuts and pie as a housewarming gift! The brownies are still at your old place, but in your excitement about the move, you’ve forgotten all about them.

Creating Deeply Immutable Lists

The solution to creating an immutable list is to mark the variable name with the const keyword. This forces the list to be deeply immutable, meaning every element of the list must also be a compile-time constant.

Const Variables

Replace the body of main with the following example:

const desserts = ['cookies', 'cupcakes', 'donuts', 'pie'];
desserts.add('brownie'); // not allowed
desserts.remove('pie'); // not allowed
desserts[0] = 'fudge';  // not allowed
UnsupportedError (Unsupported operation: Cannot add to an unmodifiable list)

Const List Literals

If you’re not able to use const for the variable itself, you can still make the value deeply immutable by adding the optional const keyword before the list literal:

final desserts = const ['cookies', 'cupcakes', 'donuts', 'pie'];
class Desserts {
 Desserts([this.desserts = const ['cookies']]);
 final List<String> desserts;
}

Unmodifiable Lists

Finally, if you want an immutable list but won’t know the element values until runtime, you can create a list with the List.unmodifiable named constructor:

final modifiableList = [DateTime.now(), DateTime.now()];
final unmodifiableList = List.unmodifiable(modifiableList);

List Properties

Collections such as List have several properties. To demonstrate them, use the following list of drinks:

const drinks = ['water', 'milk', 'juice', 'soda'];

Accessing First and Last Elements

Access the first and last element in a list with first and last:

drinks.first // water
drinks.last  // soda
drinks[0]                  // water
drinks[drinks.length - 1]  // soda

Checking If a List Contains Any Elements

You can also check whether a list is empty or not.

drinks.isEmpty   // false
drinks.isNotEmpty // true
drinks.length == 0 // false
drinks.length > 0 // true

Looping Over the Elements of a List

When you have a collection like a list, you often need to perform some action on or with each list element. As you learned in Chapter 5, “Control Flow”, loops are a great way to perform a repeated task.

Using a For Loop

To perform an action on each list element using a for loop, you’ll need to combine your knowledge of loops with what you learned about using an index to access the list elements.

const desserts = ['cookies', 'cupcakes', 'donuts', 'pie'];

for (int i = 0; i < desserts.length; i++) {
 final item = desserts[i];
 print('I like $item.');
}
I like cookies.
I like cupcakes.
I like donuts.
I like pie.

Using a For-In Loop

It’s such a common activity to iterate over the elements of a collection that Dart provides a special loop precisely for this purpose. It’s called a for-in loop. These loops don’t have any sort of index or counter variable associated with them, but they make iterating over a collection convenient.

for (final item in desserts) {
 print('I also like $item!');
}
I also like cookies!
I also like cupcakes!
I also like donuts!
I also like pie!

Exercise

Start with the following list of numbers:

const numbers = [1, 2, 4, 7];

Code as UI

The Flutter framework chose Dart because of its unique characteristics. However, Flutter has also influenced the development of Dart. You can see this with the following additions to the Dart language:

Spread Operator (...)

You can combine lists in a few ways. Start by creating the following two lists so you have something to work with:

const pastries = ['cookies', 'croissants'];
const candy = ['Junior Mints', 'Twizzlers', 'M&Ms'];
final desserts = ['donuts'];
desserts.addAll(pastries);
desserts.addAll(candy);
print(desserts);
[donuts, cookies, croissants, Junior Mints, Twizzlers, M&Ms]
const desserts = ['donuts', ...pastries, ...candy];
const desserts = [
 'donuts',
 ...pastries,
 ...candy,
];
[donuts, cookies, croissants, Junior Mints, Twizzlers, M&Ms]

Collection if

When creating a list, you can use a collection if to determine whether to include a particular element.

const peanutAllergy = true;

const sensitiveCandy = [
 'Junior Mints',
 'Twizzlers',
 if (!peanutAllergy) 'Reeses',
];
print(sensitiveCandy);
[Junior Mints, Twizzlers]

Collection for

There’s also a collection for, which you can use within a list to generate elements based on another list.

const deserts = ['gobi', 'sahara', 'arctic'];
var bigDeserts = [
 'ARABIAN',
 for (var desert in deserts) desert.toUpperCase(),
];
print(bigDeserts);
[ARABIAN, GOBI, SAHARA, ARCTIC]

Handling Nullable Lists

Thus far in this chapter, you haven’t had to worry about nullable values. You’ll need to consider them, though.

Nullable Lists vs. Nullable Elements

A few possibilities exist when dealing with null values and lists. Either the list itself could be null, or the values within the list could be null.

Nullable Lists

For example, you might have a list where the list itself is null. Here’s what that would look like:

List<int>? nullableList = [2, 4, 3, 7];
nullableList = null;

Nullable Elements

In contrast to that, you might have a list where one or more of the elements are null:

List<int?> nullableElements = [2, 4, null, 3, 7];

Nullable Lists With Nullable Elements

Finally, you can also have a nullable list with nullable elements:

List<int?>? nullableListAndElements = [2, 4, null, 3, 7];
nullableListAndElements = null;

Using the Basic Null-Aware Operators

Everything you learned in Chapter 11, “Nullability”, applies to handling nullable lists or nullable elements.

List<String?>? drinks = ['milk', 'water', null, 'soda'];
// 1
for (String? drink in drinks) {
 // 2
 int letters = drink?.length ?? 0;
 print(letters);
}
4
5
0
4

Using Null-Aware Collection Operators

In addition to the standard ways of handling null that you’ve learned, two operators apply specifically to lists:

Null-Aware Index Operator (?[])

The null-aware index operator (?[]) is used to access a list’s elements when the list itself might be null.

List<String>? myDesserts = ['cake', 'pie'];
myDesserts = null;
String? dessertToday = myDesserts?[1];

Null-Aware Spread Operator (…?)

There’s also a null-aware spread operator (...?), which will omit a list if the list itself is null.

List<String>? coffees;
final hotDrinks = ['milk tea', ...?coffees];
print(hotDrinks);
[milk tea]

Challenges

Before moving on, here are some challenges to test your knowledge of lists. It’s best to try to solve them yourself, but solutions are available with the supplementary materials for this book if you get stuck.

Challenge 1: Longest and Shortest

Given the following list:

const strings = ['cookies', 'ice cream', 'cake', 'donuts', 'pie', 'brownies'];

Challenge 2: Repetitious Repeats

How can you tell if a list contains duplicates?

final myList = [1, 4, 2, 7, 3, 4, 9];

Challenge 3: Sorting it All Out

Write an algorithm to sort a list of integers without using the sort method. If you need some help, search online for “bubble sort” and then implement that algorithm in Dart.

Key Points

  • Lists store an ordered collection of elements.
  • List elements are accessible using a zero-based index.
  • The elements of a list are mutable by default.
  • The for-in loop is a convenient way to iterate over the elements of a list.
  • The spread operator (...) allows you to expand one list inside another.
  • Collection if and for can be used to create the content of a list dynamically.
  • The nullable collection operators ?[] and ...? provide additional ways of dealing with nullable lists.

Where to Go From Here?

You saw in the “Creating Deeply Immutable Lists” section above that if you try to modify an immutable list, you won’t discover your mistake until after you run your program. Runtime mistakes are more difficult to track down. A good practice is to write tests to ensure your code works as intended. Do a web search for “unit testing” and “test driven development” to learn more. Dart has strong support for testing with the test package on pub.dev.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.

Unlock now