skip to Main Content

I have code that is working as desired, but I don’t understand how.

I have two Lists:

List<ProductInfo>_filter_data = [];
List<ProductInfo>masterData = [];

On app startup, build masterData from a datasheet. Within the app, filter the data based on user actions;

List<ProductInfo> results = [];      
      results = masterData.where((user) => type == user.selectedType ).toList(); //simplifed example

    setState(() {
      _filter_data = results;
    });

Data is displayed via a ListView based on filter_data. Users can mark their favorites via a checkbox:

   setState(() {

          if (isChecked) {
            _filter_data[widget.data_index].fav = true;
          }
          else {
            _filter_data[widget.data_index].fav = false;
          }

I wrote this quickly last night, and it appeared to be working. Today I’m questioning why. When users click a checkbox the _filter_data list is updated. But based on my understanding, the masterData list wouldn’t be. So as soon as a new filter is applied, and a new _filter_data is made based on the masterData, the favorite selection should be lost.

But that’s not what’s happening. Both the _filter_data and master_data lists are getting the .fav tag updated. I wrote some debug print statements that show both the _filter_data and masterData showing the fav being updated. How? Is the ProductInfo item shared between lists?

To be clear, this is the desired behavior (I want the fav data saved to masterData). I just don’t understand how it is working as written.

2

Answers


  1. Your List masterData contains objects. Your List results contains some of those objects, but does not create a copy of those objects. They are the same objects. Both Lists contain references to these same objects.
    Then you hand over the reference to this List of objects called results to _filter_data. Again, this is a List of objects that already existed in the List called masterData.

    If you manipulate an object in a List, while the same object is also referenced in another List, then the change of this specific object is visible where ever it is referenced.

    Login or Signup to reply.
  2. 1. Explanation

    Hi, I have to say that you are working with mutable Lists so this is what happen:
    You create an original list that fetch the data probably from a Database of a static data source:

    List<ProductInfo> masterData;
    

    Initialized as:

    masterData = dataSource.getData(); //Any data source (local or remote)
    

    After your masterData is initialized, you filter this list and put the result of that filter on _filter_data, you just showed us how.

    You mutate the objext at ‘i’ position and asked why it also mutates the masterData as well.

    _filter_data[widget.data_index].fav = true//Or false;
    

    The answer is that the list is a list of mutable object references like in the next example:

    //This is illustrative, a dart list is not declared in this way
    List<ProductInfo> masterData = [ProductInfo1, ProductInfo2, ProductInfo3, ProductInfo4, ProductInfo5];
    

    After that, you filter the data and a possible state after of apply the filter is a list of two filtered items:

    //This is also illustrative
    List<ProductInfo> _filter_data = [ProductInfo2, ProductInfo4]; 
    

    With this, you can see that the objects are being referenced and the ProductInfo2 is the same in both lists.

    So if you mutate the ProductInfo2 using either masterData or _filter_data lists you will mutate the same object.

    This is mutating the _filter_data[0] that could be any object at masterData[x]:

    //So if you mutate this object
    _filter_data[0].fav = true;
    //You will also be mutating the fav property of materData[x] object
    

    Thereby the next mutation is the equivalent of the previous one:

    //So if you mutate this object
    masterData[x].fav = true;
    //You will also be mutating the fav property of _filter_data[x] object
    

    2. And maybe you are asking; how to avoid this?

    To avoid this, I can give you two options:

    Option 1: Using the map method

    Create manual copies of the objects after the filter action:

    List<ProductInfo> tempFilteredData= masterData.where((user) => type == user.selectedType ).toList();
    _filter_data = tempFilteredData.map((originalProductInfo)=>
      ProductInfo(
        property1: originalProductInfo.property1, 
        property2: originalProductInfo.property2,
    )).toList();
    

    This way you are creating new objects that are not being referenced at masterData object list.

    Option 2: Using a package like freezed

    With this library you can copy the object more easily like:

    List<ProductInfo> tempFilteredData= masterData.where((user) => type == user.selectedType ).toList();
    _filter_data = tempFilteredData.map((originalProductInfo)=>
      originalProductInfo.copyWith()).toList();
    

    I hope this can help you.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search