Chapter 4. Handling Lists and Maps

In this chapter, the fundamentals of data handling with Dart are outlined. The aim of this chapter is to cover Lists and Maps that are used to provide foundational data structures for information handling in Dart.

If you are lucky enough to be familiar with other languages, then many of the concepts presented should be familiar. However, in case this is your first time seeing these techniques, the example pieces of code are self-contained. You may find it helpful to run and experiment with the examples to gain a feel of the workings of the language.

The chapter begins by discussing Lists, which are indexable data structures used to hold objects. Lists are very common elements in Dart, as they can be used in a variety of scenarios.

We also discuss how to use Maps, which are useful for handling key/value pairs. A key/value pair is an associative relationship where the key is used as an index to access a value. For example, you may have a months of the year data structure in which the key is a number and the value is derived from the number, e.g., 1 generates the month January and 12 denotes December.

Over the course of this chapter, you will learn how to utilize Maps and Lists within your application.

4.1 Creating Lists of Data

Problem

You want a way to use a list of values within a Dart application.

Solution

Use a List to organize objects as an ordered collection. A List represents an array object that can hold information. It provides a simple construct that uses a zero-indexed grouping of elements.

Here’s an example of how to use a List in Dart:

void main() {
  List listMonths = ['January', 'February', 'March'];

  listMonths.forEach(print);
}

Discussion

Lists are very versatile and can be used in a variety of circumstances. In the preceding example, a List class is used to hold the months of the year, as shown in Figure 4-1.

Figure 4-1. List data structure

The List declaration is used to hold a String, but it can actually hold a variety of data types, making this object extremely flexible. The List class provides a number of helpful methods such as forEach, length, reverse, isEmpty, and isNotEmpty. Each element within the List is directly addressable as well as being capable of being accessed via a forEach method.

A List is denoted by the use of square start and end brackets. Within the square brackets are the List elements, separated by commas. List items can be initialized at declaration or amended at a later time during processing.

The length of the List is available as a method, and this is used to identify how many elements are currently available. Note the List is indexed from zero, so if you intend to manually access elements, you will need to use zero if you want the first element.

Another nice feature of Lists is that they include a range of methods to handle processing information. In the example, the forEach method is used to perform a print of the elements contained in the List.

As you become more confident with Dart, Lists will become one of the many tools that are essential in the applications you write.

4.2 Amending a List of Data

Problem

You want to add new content to an existing List.

Solution

Use the List add method to incorporate new content into a List. Lists support the dynamic addition of new elements and can be expanded as required.

Here’s an example of how to add a List element in Dart:

void main() {
  List listMonths = ['January', 'February', 'March'];

  listMonths.add('April');

  listMonths.forEach(print);
}

Discussion

In the preceding example code, a List is initially defined with three elements. If you want to expand the number of elements, this can be done by using the List add method. The add method will append the new element at the end of the List.

When you append a new element, as in Figure 4-2, the List class takes care of all the associated processing. The method add knows how to append the information passed and will ensure the relevant class properties (e.g., length) are updated. Therefore, in the example, you would see the months output as “January,” “February,” “March,” “April.” You will also see that the length of the List is amended to reflect that a fourth item has been added.

Figure 4-2. Adding an item to a List

The dynamic nature of a List makes it perfect for multiple situations where data structure manipulation is required. You will see Lists used across a number of situations to handle a variety of data types.

4.3 Using Lists with Complex Types

Problem

You want to make a List based on a combination of Strings and integers to create a new complex data type.

Solution

Use Lists to organize the consolidation of other data types. Lists can be especially useful for handling other data structures such as Maps (see Recipe 4.4).

Here’s an example of how to use a List with complex data types in Dart:

void main() {
  Map<String, dynamic> filmStarWars = {"title": "Star Wars", 
                                       "year": 1977};
  Map<String, dynamic> filmEmpire   = {"title": "The Empire Strikes Back",
                                       "year": 1980};
  Map<String, dynamic> filmJedi     = {"title": "The Return of the Jedi",
                                       "year": 1983};

  List listFilms = [filmStarWars, filmEmpire, filmJedi];

  Map<String, dynamic> currentFilm = listFilms[0];

  print(currentFilm);
  print(currentFilm['title']);
}

Discussion

In the example, film data is added to a Map that encloses title and year information, as shown in Figure 4-3. Here we use a List to manage the individual Maps, so the individual elements can be combined. The resultant List provides a convenient data structure for accessing the information to be stored.

Figure 4-3. List with complex data type

Accessing the information within the List follows the same process as a normal List. To access the information, you need to dereference the variable. In this context, to dereference, use an index to tell Dart that you want to access the property value. The listFilms[0] means to access the first element in the List.

Once you have access to the List, the data type can then be accessed based on the associated data type. The example code uses a Map to allocate multiple values together. As each element is a Map, you now have the data associated with this. Use the dereferenced value to store in a new variable currentFilm, which can be accessed directly or with a key.

Using Lists can provide an elegant method to access complex data types in a consistent manner. If you need to coordinate data types, consider using a List to make this process more manageable.

4.4 Handling Map Key/Value Pairings

Problem

You want to handle a key/value pair in a Dart application.

Solution

Use a Map to handle key/value objects of any type. Map keys are required to be unique, as they act as the index to access Map values. Map values are not required to be unique and can be duplicated as needed.

Here’s an example of how to declare a Map in Dart:

void main() {
  Map<int, String> mapMonths  = {0: 'January', 1: 'February', 2: 'March'};
  Map<int, String> moreMonths = {3: 'April', 4: 'May'};

  mapMonths.addEntries(moreMonths.entries);
  
  mapMonths.forEach((key, value){
    print('$key: $value');
  });
}

Discussion

In the code example, a Map is used to define a collection of data based on month information. A construct of this type is very useful to enable pieces of information to be combined together that benefit from a key/value pairing, as shown in Figure 4-4.

Figure 4-4. Map data structure

The month Map structure builds a relationship between the key and the value, e.g., Month 0 is January and January is Month 0. The structure of a Map is very useful in terms of processing list information, such as JavaScript Object Notation (JSON).

Declaration of the Map follows a standard variable format. Note: Map is actually a function call, so this requires the addition of braces. In our example, we indicate that the Map will be composed of an integer and a string, which means a number is used for the key (i.e., index). The string value field holds the reference to the month. You could also define the value as dynamic, which would allow the use of different variable types, providing additional flexibility. To be more specific, you could introduce the dynamic definition to explicitly replace the string. If you make this change, Dart will automatically infer the correct data type based on the variable assignment.

To populate the Map, define a key (e.g., mapMonth) and then assign a value (e.g., January). The assignment of values can be made in any order; the important thing is to be careful not to duplicate the keys used.

Adding an element to the Map requires both a key and a value. In the example, an additional Map structure called aprilMonth is created. Add the new month to the existing structure by calling the addEntries method with the parameter of the newMonth Map.

To access the information within the Map class, use the Map methods and properties. A Map has a number of methods available that can be used to access the associated data items. To loop through each item, use the key to access the individual items. The example uses the Map forEach method to access each data structure item. With access to the key, the information associated with the Map can be retrieved by combining these elements together.

Regarding access to data, it is always worth checking the style guide information provided by the Dart team. In this instance, rather than use a forEach, you could also use a for loop. The guidance associated with access is defined in the Dart documentation.

As the Dart language matures and guidelines are changed, it is good to be able to understand the reasoning behind them. The general tip to you the developer is to keep an awareness of these changes and look to respond to general guidance to avoid more complex refactoring in later development stages.

4.5 Printing Map Data Structure Content

Problem

You want to output the value of a Map data variable.

Solution

Use a Map to reference an indexed item. Map values are referenced as key/value combinations, which can be assigned to a variable for easier access.

Here’s an example of retrieving a Map property and outputting the value with Dart:

void main() {
  Map<int, String> mapMonths = {0: 'January', 1: 'February', 2: 'March'};

  print ("Month: ${mapMonths[0]}");
  print ("Map: $mapMonths");
}

Discussion

In the code example, the value within the Map structure is accessed via its integer key. When a value is required, we provide the key to index the Map and retrieve the desired value. The first print statement explicitly requests the information associated with the key [0]. The use of braces around the variable is required to ensure we output the value of the month. In the second print statement, we output the entire Map for the mapMonths variable.

If you don’t want to create individual variables to hold the values, remember to dereference the Map values. In the print statement the braces are added to indicate to the Dart compiler we want the variable values rather than the contents of the Map.

4.6 Validating That Content Exists in a Map

Problem

You want to confirm that a key exists in a Map.

Solution

Use the indexing functionality of a Map to identify if a key explicitly exists.

Here’s an example of validating that a key exists in a Map with Dart:

void main() {
  Map<int, String> mapMonths = {0: 'January', 1: 'February', 2: 'March'};

  if (mapMonths[0]!=null) {
    print ('Test 1: Key exists');
  } 
  
  if (mapMonths.containsKey(2)) {
    print('Test 2: Key exists');
  }
}

Discussion

Maps are indexed  using key values, so validating the existence of a key can quickly be performed. To find a key, use the required key to index the Map.

In the example, the Map will return a null value where the key is not present in the Map. An explicit check can be performed to ascertain if the value exists by checking for a non-null return. An alternative check would be to use the containsKey method to verify the value exists. From a functional perspective, the if statements are performing the same check.

Stylistically, you may prefer one approach over another. If the key exists in the Map, the information returned will be the value associated with the key.

4.7 Printing Complex Data Types

Problem

You want to output a variable based on a complex data type.

Solution

Use a print statement to display information from an application. The print statement will need to be formatted to correctly interpolate the data type to be displayed.

Here’s an example of a how to print the complex data type:

import 'dart:convert';

void main() {
  // Create JSON value
  Map<String, dynamic> data = {
    jsonEncode('title'): json.encode('Star Wars'),
    jsonEncode('year'): json.encode(1977)
  };
  
  // Decode the JSON
  Map<String, dynamic>  items = json.decode(data.toString());
  
  print(items);
  print(items['title']);
  print("This is the title: $items['title']");
  print('This is the title: ${items['title']}');
}

Discussion

Use the $ character to reference a variable in a print statement. Prefixing a variable with the $ tells Dart that a variable is being used and it should replace this value.

The following examples from the last two print statements illustrate use cases that you will come across where the variable value needs a bit of help to be displayed correctly.

In the first use case, there is String interpolation without braces:

print("This is the title: $items['title']");

In the second use case, there is String interpolation with braces:

print('This is the title: ${items['title']}');

The preceding statements look equivalent, but they are not. In the first print statement, Dart will interpret the variable to be displayed as items. To display the variable correctly, it actually requires that it be enclosed in braces, as per the second line. Doing this will ensure that the value associated with items['title'] is correctly interpreted by the print statement.

Get Flutter and Dart Cookbook now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.