Osama Remlawi
Osama Remlawi

Reputation: 2990

How to perform deep copy for a list to another list without affecting the original one in Flutter

I have two lists of Type object, _types and _filteredTypes:

List<Type> _types = []; // Original list
List<Type> _filteredTypes = []; // Where i bind filter the contents

a Type object is:

class Type extends Equatable {
  final int id;
  final String title;
  List<SubType>? subTypes;

  Type({
    required this.id,
    required this.title,
    this.subTypes,
  });
}

a SubType object is:

class SubType extends Equatable {
  final int id;
  final String title;

  Type({
    required this.id,
    required this.title,
  });
}

I need to filter the list based on search text, so whenever user types a letter the _filteredTypes being updated.

I do the filter on _onSearchChangedEvent() within setState() like this:

_filteredTypes = List.from(_types); // To get the original list without filter

for(var i = 0; i < _filteredTypes.length; i++) {
    // This is where i filter the search result when a subType.title matches the search query:
    _filteredTypes[i].subTypes = List.from(_types[i].subTypes!.where((element) => element.title.toLowerCase().contains(query!.toLowerCase())).toList());
}

// This is where i filter the search result and remove any type doesn't match any subType.title:
_filteredTypes.removeWhere((element) => element.subTypes!.length == 0);

bindTypes(); // To refresh the list widget

The problem is when i need get the original list i get the main type but type.subTypes is still filtered based on the previous search not the original one! even it is copied without reference _filteredTypes = List.from(_types);

It seems like a bug in Flutter, any idea guys?

Upvotes: 1

Views: 1374

Answers (2)

Osama Remlawi
Osama Remlawi

Reputation: 2990

This is how to perform a deep copy for a list has sub-list, thanks moneer alhashim, your answer guided me.

I'm posting it as an answer to help someone else find the solution easy.

So, the key here is to map the original list types.map(...) and fill it manually instead of using List.from(), and that will create a new instance for deep objects.

First, i declared one function for each list:

//For the parent list:

List<Types> getNewTypesInstance {
    // This function allows to perform a deep copy for the list.
    return types.map((e) =>
        Type(id: e.id, title: e.title, subTypes: e.subTypes))
        .toList();
}


// For the child list:

List<SubType> getNewSubTypesInstance(List<SubType> lst) {
    // This function allows to perform a deep copy for the list:
    return lst.map((e) =>
        SubType(id: e.id, title: e.title))
        .toList();
}

And if you have more deep list(s), you will need third function to obtain it as new instance and so on.

Finally, the way how to call them is to write this code within setState():

_filteredTypes = getNewTypesInstance

for(var i = 0; i < _filteredTypes.length; i++) {
    // This is where i filter the search result when a subType.title matches the search query:
    _filteredTypes[i].subTypes = List.from(getNewSubTypesInstance(_types[i].subTypes!.where((element) => element.title.toLowerCase().contains(query!.toLowerCase())).toList()));
}

// This is where i filter the search result and remove any type doesn't match any subType.title:
_filteredTypes.removeWhere((element) => element.subTypes!.length == 0);

bindTypes(); // To refresh the list widget

Upvotes: 0

moneer alhashim
moneer alhashim

Reputation: 828

List.from does not provide you with a deep copy of _types- it gives you a shallow copy. Meaning both _filteredTypes and _types share the same subTypes. It's similar in that behavior to this example

var sharedList = [1];
final listOne = [1, sharedList];
final listTwo = [1, sharedList];
sharedList[0]  = 2;

Changing sharedList will change the value in both listOne and listTwo. If shared list was just an integer, changing that integer would not produce the same effect. Like in this example:

var sharedInteger = 1;
final listOne = [1, sharedInteger];
final listTwo = [1, sharedInteger];
sharedInteger  = 2;

When you create an instance of a class may it be built in like List or your own custom class, what you get returned is a reference or a pointer to that instance/object. The object itself is allocated on the heap memory area rather than on the stack which means that this object can be referenced outside of functions. As in its life (object life) is not bound by the scope of the function so when you reach the end } of the function the object still exists, and its memory is freed by a special program called the garbage collector.

In dart as in many modern programming languages garbage collectors are used and objects are automatically allocated on the heap. In languages such as C++ for example you can allocate objects on the stack, and you have to be explicit about heap allocation, and deallocate any objects on the heap when you are done with them.

All of the above you can look up the gist of it is since subtypes is a list, it's a reference type so both _filteredTypes and _types have that reference. If you want a deep copy you can do that as well, and I'll leave it for you to look that up.

Upvotes: 1

Related Questions