Boost App Reliability: Shallow vs. Deep Object Cloning in Flutter πŸ†

Learn the best way to copy object in Flutter and improve your flutter app reliability.

Abdul Rehman
4 min readSep 3, 2023

What are different ways to copy an object in Flutter? πŸ€”

You can create a copy of an object in two ways, namely Shallow copy and Deep copy. The object referred here are complex object which contains lists, class instances etc.

This is not Dart specific feature, many programming languages some have built in ways to handle it, and some don't.

⚠️ Shallow Copy ⚠️

Shallow copy creates a new object that shares the same memory with the original object. Both the object shares the same reference and content inside them. Let see an example of shallow copy:

class Traveller {
final Name name;
final int age;

const Traveller(this.name, this.age);
}

class Name {
String name;

Name(this.name);
}

void main() {
final traveller = Traveller(Name('Abdul'), 28);

/// Creating shallow copy
final copy = Traveller(traveller.name, traveller.age);
}

The copy variable created above is a shallow copy of traveller. In theory, we are creating a new object by reusing traveller but both shares same memory which create unexpected side effects. 😱

Let’s try to update values of the above variables.

void main() {
final traveller = Traveller(Name('Abdul'), 28);
final copy = Traveller(traveller.name, traveller.age);

copy.name.name = 'Abdul (changed)';

print(traveller.name.name); /// Abdul (changed)
print(copy.name.name); /// Abdul (changed)
}

We changed the name of the copy object, but the name of the original traveller object also got changed. This is a typical side effect of shallow object. ⚠️ As both object share the same memory, on updating one object, the other also updates automatically ⚠️. This creates unexpected behavior in our flutter project. We will learn how to fix this issue in the next section.

πŸ† Deep Copy πŸ†

Deep copy creates a new object and copy all data of old object with new instances (in shallow copy, they share same instance). All the properties of old object are copied to new object without sharing any reference to old object.

In Dart we can create deep copy using copyWith method. Let's add support of copyWith in our Traveller class.

class Traveller {
final Name name;
final int age;

const Traveller(this.name, this.age);

Traveller copyWith({Name? name, int? age}) {
return Traveller(
name ?? Name(this.name.name),
age ?? this.age,
);
}
}

The above copyWith method can create a deep copy of an object. It can either create exact copy of new object or create copy with modified values. Let see copyWith in action.

void main() {
final traveller = Traveller(Name('Abdul'), 28);
final copy = traveller.copyWith();

copy.name.name = 'Abdul (changed)';

print(traveller.name.name); // Abdul
print(copy.name.name); // Abdul (changed)
}

In the above example, we create a new object copy which is a deep copy of traveller. Both objects are independent (unlike shallow which share same memory), so on updating one object does not affect the value of other object. ✌️

void main() {
final traveller = Traveller(Name('Abdul'), 28);
final copy = traveller.copyWith(age: 29);

copy.name.name = 'Older Abdul';

print('${traveller.name.name}, ${traveller.age}'); // Abdul, 28
print('${copy.name.name}, ${copy.age}'); // Older Abdul, 29
}

In the above example, copyWith still create a deep copy of traveller but with different value. On updating any object does not affect other one.

Deep copy of list πŸ€”

When a creating a deep copy of a list, we need to make sure that all individual item in the list are deeply copied.

class Passengers {
final List<Traveller> travellers;

const Passengers(this.travellers);

Passengers copyWith({List<Traveller>? travellers}) {
if (travellers != null) {
return Passengers(travellers);
}
/// Deep copy logic
final deepList = <Traveller>[];
for (final traveller in this.travellers) {
deepList.add(traveller.copyWith());
}
return Passengers(deepList);
}
}

In the above example, we transverse over all element and make a deep copy of each element in the list. In this way, we are guaranteed that there is no reference is shared across the two copies.

Summary ✍️

Whenever you want to create a copy of an object, always use copyWith method. This will guarantee that the program does not misbehave on the basis of the new object. Always create a copyWith method in your classes to easily use it in your project.

To streamline the process of creating copyWith methods, you can use build_runner and annotate your class using copy_with_extension_gen plugin for ease of use.

Thank you, and I hope this article helped you in some way. If you like the post, please support the article by sharing it. Have a nice day!πŸ‘‹

I am an mobile enthusiast πŸ“±. My expertise is in Flutter and Android development, with more than 5 years of experience in the industry.

I would love to write technical blogs for you. Writing blogs and helping people is what I crave for 😊 .You can contact me at my email(abdulrehman0796@gmail.com) for any collaboration πŸ‘‹

πŸ”— LinkedIn: https://www.linkedin.com/in/abdul-rehman-khilji/

--

--

Abdul Rehman

Mobile developer in love with Flutter and Android β™₯️