Dart/Flutter: What does copyWith() do?

Paul Halliday

Although the notion of copyWith() isn't specifically related to Dart or Flutter, the pattern is used quite often and I see this question pop up all the time.

The primary benefit of using copyWith() is that you don't mutate the original object, but instead return a new object with the same properties as the original, but with the values you specify. This allows you to create applications that are easier to test and easier to maintain as objects themselves don't harbor mutable state.

Instead of mutating or copying an object, you can use copyWith() to create a new object with the same properties as the original, but with some of the values changed.

Let's take a look at how it's used, as well as ways to create our own copyWith function!  

Immutable copyWith uses in Flutter

This pattern is commonly used when theming items within a Flutter application, such as taking a textTheme and overriding properties:

Text(
  "I'm using a custom title",
  style: Theme.of(context)
              .textTheme
              .headline
              .copyWith(
                color: Colors.red, 
                fontWeight: FontWeight.bold),
)

Here we're copying the headline theme and overriding some values such as color and fontWeight.

As this is a copy, the original theme is not affected and the other values remain unchanged.

You'll find that this immutable, copy first pattern is used in other places such as withOpacity:

Colors.red.withOpacity(1.0)

How to make your own copyWith function

You can make your own copyWith function by taking in the values you want to change and returning a new object with the new values.

Here's an example with a Product class:

class Product {
  final String id;
  final String name;
  final Color color;

  Product({this.id, this.color, this.name});

  Product copyWith({String id, String name, Color color}) => Product(
        id: id ?? this.id,
        name: name ?? this.name,
        color: color ?? this.color,
      );
}

Within the copyWith function we're taking in the potential overrides such as id, name and color. We're then returning a new Product with either the current values, or replacing those values with the ones passed in.

We can use this to display a list of our dining room tables that we have for sale. They both have the same name, but have a different id and color:

class _HomePageState extends State<HomePage> {
  List<Product> products = [];

  
  void initState() {
    super.initState();

    Product sixSeaterDiningTableBrown = Product(
      id: "0",
      name: "6 Seater Dining Table",
      color: Colors.brown,
    );

    Product sixSeaterDiningTableBlack =
        sixSeaterDiningTableBrown.copyWith(color: Colors.black, id: "1");

    products.add(sixSeaterDiningTableBrown);
    products.add(sixSeaterDiningTableBlack);
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Hello, copyWith!"),
      ),
      body: ListView(
          children: products
              .map((Product product) => ListTile(
                    trailing: Container(
                      width: 14,
                      height: 14,
                      color: product.color,
                    ),
                    title: Text(product.name),
                    subtitle: Text(product.id),
                  ),
                )
              .toList(),
            ),
    );
  }
}

In this instance, we have two items visible in the ListView. The first item is the sixSeaterDiningTableBrown and the second is the sixSeaterDiningTableBlack.

The id and color are different, but the name is the same. We used the immutable copyWith function to create the sixSeaterDiningTableBlack instead of directly copying the sixSeaterDiningTableBrown.

Although this is a small example, you can see how this pattern can be used to work with data in larger applications.

Conclusion

Immutability allows you to create code that is easier to reason about, as you no longer need to track internal changes to an object. This is especially useful when you're working with data that is coming from a remote source.

Use copyWith to create a new object with the same properties as the original, but with some of the values changed. You can then apply these techniques with Freezed to make the process of creating and working with immutable structures easier.

Paul Halliday's avatar
Paul Halliday's avatar

Paul Halliday

Creator ● developer.school

Passionate about cross-platform web and mobile development.

developer.school

© 2021 developer.school. All rights reserved.

© 2021 developer.school | All rights reserved

Subscribe to our newsletter

The latest news, articles, and resources, sent to your inbox weekly.