Changing elements: map and expand

on Monday, 20th of July, 2020

Along with the lesson on where, I'd consider map the other most important functional method on iterables. At my day job, we use List.map a lot in our Flutter UI.

In a nutshell, map is called on a list to generate a new list, where the elements are created by performing an action on each of the original elements. That action is passed in as a callback.

Mapping is best explained with an example.

Suppose you are trying to display a list of Users in an app. But, you only want to display the user's name. There's a map for that. :}

    
class User {
    User(this.name, this.age);
    final String name; 
    final int age;
}

void main() {
    final rody = User("Rody", 25);
    final jorge = User("Jorge", 25);
    final remi = User("Remí", 25);

    final users = [rody, jorge, remi];
    final names = users.map((User user) => user.name);
    print(names);
}

As mentioned, map is used a lot in UI work, which I'll show in a later lesson on iterables in Flutter widgets. This is just a good visual example, though. I think map is used more than most other methods from the Dart SDK in my companies app, throughout the business logic as well.

To be clear, map isn't only useful when changing elements into different types entirely. You could also use it update all the elements in an object:

    
class User {
    User(this.name, this.age);
    final String name; 
    final int age;

    bool isSpecialUser = false;
  
    String toString() {
      return 'User {name: $name, age: $age, isSpecialUser: $isSpecialUser}';
    }

}

void main() {
    final rody = User("Rody", 25);
    final jorge = User("Jorge", 25);
    final remi = User("Remí", 25);

    final newUsers = [rody, jorge, remi];
    print(newUsers);

    final asSpecialUsers = newUsers.map((User u) => u.isSpecialUser = true);
    print(asSpecialUsers);
}

Using expand

Expand is an interesting one. It's not used nearly as much as map, but it allows you to change the values in a list in some interesting ways. It has two common use cases:

  • flattening nested lists
  • duplicating elements in a list (which you can also change as you're duplicating);

First, take a look at the examples from Dart docs. They're contrived, but allow you to see some important points. Then, you'll have the opportunity to give it a try using an actual example from the app that I work on at my day job.

/// example ripped directly from the Dart docs.
/// flattening a list
var pairs = [[1, 2], [3, 4]];
var flattened = pairs.expand((pair) => pair).toList();
print(flattened); // => [1, 2, 3, 4];

/// duplicating elements in a list
var input = [1, 2, 3];
// important! the callback passed to expand must return an iterable!
var duplicated = input.expand((i) => [i, i]).toList();
print(duplicated); // => [1, 1, 2, 2, 3, 3]

This example, again, isn't super obvious or helpful for real life. Take a stab at the exercise below, which is inspired by an actual use of expand in the app that I work on at my day job.

Try it yourself: expand

Suppose you're building an e-commerce site. Your site has the functionality to show a user everything they've ever purchased. Your data might look like this:

class User {
  final List<Order> pastOrders;
  // rest of class
}

class Order {
  final List<Item> itemsInOrder;
  // rest of class
}

class Item {
  final String name;
  // rest of class
}

In order to show a user which items they've bought, and not orders they've made, you'd likely want to extract each line item from each of their orders. There are many ways to do this, including using expand. For this exercise, try to derive that list of line items from the user.



Join thousands of Flutter developers.

Sign up for infrequent updates about Flutter and Dart.

You can get all this content and more in one place. Check out my new book Flutter in Action