Copy-With topic

dart_mappable can generate a powerful copyWith method for your classes. It supports assigning null as well as chained deep copies, polymorphic copy, merging objects or applying delta maps.

@MappableClass()
class Person with PersonMappable {
  String name;
  int? age;
    
  Person(this.name, this.age);
}

void main() {
  var person = Person('Tom', 20);

  // `age` not passed, its value is preserved
  print(person.copyWith(name: 'Max')); // Person(name: Max, age: 24)
  // `age` is set to `null`
  print(person.copyWith(age: null)); // Person(name: Tom, age: null)
}

Deep Copy

When having complex nested classes, this syntax can get quite verbose. Therefore this package provides a special syntax for nested classes, similar to freezed.

Consider the following classes:

@MappableClass()
class Person with PersonMappable {
  String name;

  Person(this.name);
}

@MappableClass()
class Company with CompanyMappable {
  Person manager;
  List<Person> employees;
  
  Company(this.manager, this.employees);
}

void main() {
  var company = Company(Person('Anna'), [Person('Max'), Person('Tom')]);
  
  // access nested object using the 'dot' syntax
  print(company.copyWith.manager(name: 'Laura')); 
  // prints: Company(manager: Person(name: 'Laura'), ...)
  
  // this also works with lists or maps
  print(company.copyWith.employees.at(0)(name: 'John')); 
  // prints: Company(..., employees: [Person(name: 'John), Person(name: 'Tom')])
  
  // you can also use 'apply' with a custom function to transform a value
  print(company.copyWith.manager.apply((manager) => Person(manager.name.toUpperCase())));
  // prints: Company(manager: Person(name: 'ANNA'), ...)
}

When working with Lists or Maps and copyWith, there are different methods you can use to access, add, remove or filter elements. The complete interfaces are documented

CopyWith for Inheritance, Polymorphism and Generics

CopyWith works not only for simple use-cases, but also supports complex class structures with inheritance or generics, like Polymorphism.

After reading the next page, check out the Polymorphic-CopyWith Example on Github.

Merging Objects

You can also merge classes fields using the .copyWith.$merge() extension. Here any non-null property of the provided object will override the respective property of the base object. Other than the normal copy-with, this is not null-aware / reversible, meaning a non-null property can never be set back to null using this method.

@MappableClass()
class A with AMappable {
  A(this.a, this.b);
  
  String? a;
  String? b;
}

void main() {
  var a = A('a', null);
  var b = A(null, 'b');
  
  var c = a.copyWith.$merge(b);
  assert(c == A('a', 'b'));
}

Copy Delta

You can also apply a delta map on an object using the .copyWith.$delta extension. The delta copy-with is null aware (other than the merging copy-with) meaning that explicit null values in the provided map will override any value in the base object.

@MappableClass()
class A with AMappable {
  A(this.a, this.b, this.c);
  
  String? a;
  String? b;
  String? c;
}

void main() {
  var a = A('a', null, 'c');
  var delta = {'b': 'b', 'c': null};
  
  var c = a.copyWith.$delta(delta);
  assert(c == A('a', 'b', null));
}

Next: Polymorphism

Classes

CopyWithBase<Result, In, Out> Copy-With
ListCopyWith<Result, Elem, Copy> Copy-With
Interface used for Lists in chained copyWith methods All methods return a new modified list and do not modify the original list.
MapCopyWith<Result, Key, Value, Copy> Copy-With
Interface used for Maps in chained copyWith methods All methods return a new modified map and do not modify the original map.
ObjectCopyWith<Result, In, Out> Copy-With