flutter : Unsupported operation: Cannot add to an unmodifiable list

Created At: 2023-06-30 17:38:28 Updated At: 2023-06-30 22:37:22

Flutter: Unsupported operation: Can not add to an unmodifiable list. The other day I was working with Cubit and adding item to a List.

Dart List is growable and modifiable by nature

In our example we will be using a List. This List takes custom objects of Person type. This object just basic information about a Person.

Now, let's a look at our state class. Our state class would hold this variable and help us storing new updated data.

Here we have a List and it is returned inside copyWith() method as a part of immutable object. And how is it?

If you see carefully line 126, you will find that we added const as we initialized this.people object.

When you add const before a variable it becomes immutable

That means if it's created once, you can not change it or edit it. 

First look at our Cubit class, that holds method for changing the List.

Here you see we can get the state object and people List from it. And then we are trying to add an object. And then we counter the issue.

See that we can Can not add to an unmodifiable list.

So the reason is our Cubit holds a List that's not growable meaning that you can not add ore remove items from the List.

Solutions

There are many solutions to work with this. Either we can remove the const modifer or force it to be growable by using toList() method.

In this example, if I remove const modifier, I need to make changes in many places. So I decided to use toList() method when I get the List from the state object.

Now it looks like below

Online 106, I have called toList() method and this method makes a list modifiable.

void main() async {

  runApp( MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return BlocProvider<PersonCubits>(
      create: (context) => PersonCubits(),
      child: MaterialApp(
        home: HomePage(),
      ),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return BlocBuilder<PersonCubits, PersonStates>(builder: (context, state) {
      return Scaffold(
        body: Center(
          child: Container(
            child: ElevatedButton(
              onPressed: () {
                var person = Person('ahmed', Random().nextInt(30));
                context.read<PersonCubits>().changePerson(person);
                print(state.people.length);
                Navigator.push(context,
                    MaterialPageRoute(builder: (context) => SecondPage()));
              },
              child: Text("Tap"),
            ),
          ),
        ),
      );
    });
  }
}

class SecondPage extends StatefulWidget {
  const SecondPage({Key? key}) : super(key: key);

  @override
  State<SecondPage> createState() => _SecondPageState();
}

class _SecondPageState extends State<SecondPage> {
  @override
  Widget build(BuildContext context) {
    print('${BlocProvider.of<PersonCubits>(context).state.people!.length}');
    return Scaffold(
      appBar: AppBar(),
      body: Container(
        child: Center(
          child: ListView.builder(
              itemCount:
                  BlocProvider.of<PersonCubits>(context).state.people!.length,
              itemBuilder: (_, index) {
                print(
                    "${BlocProvider.of<PersonCubits>(context).state.people!.length}");
                var list = BlocProvider.of<PersonCubits>(context).state.people;
                return Container(
                  child: Column(
                    children: [
                      Text(list[index].name),
                      Text(list[index].age.toString())
                    ],
                  ),
                );
              }),
        ),
      ),
    );
  }
}

class PersonCubits extends Cubit<PersonStates> {
  PersonCubits() : super(PersonStates());

  changePerson(Person person) {
    var people = state.people.toList();
    people.add(person);
    emit(state.copyWith(people: people));
  }
}

class Person {
  String name;
  int age;

  Person(this.name, this.age);
}

class PersonStates {
  List<Person> people;
  Person? person;
  String? type;

  PersonStates(
      {this.person, this.type = 'good', this.people = const <Person>[]});

  PersonStates copyWith({Person? person, String? type, List<Person>? people}) {
    return PersonStates(
        person: person ?? this.person,
        type: type ?? this.type,
        people: people ?? this.people);
  }
}

Another solution

The other solution could be removing the const modifier and make changes in relative places. This does pose a new problem. 

Since earlier we are initializing the List using empty values, it was fine. Our Cubit could easily take the List(empty one) and add more items to it.

But with this new approach you will get null check used on null value error. Because if we take out the const modifer, that means we won't be able to initialize the List.

That means when our Cubit tries to look up the List values, it would be null. Nothing would be there. Cuz in Dart,

you must initialize a varibale or object before you use it.

# First we need to change our List<Person> declaration type. Previously it was not nullable. We need to make it nullable.

# Second we need to declare a new method in our Cubit. This new method would be responsibe for initializing the List with some default values. 

A List or an object must be initialize before you use it.

See the changes we made, 

  1. A new cubit method to initialize the List line 119
  2. We made the List nullable line 125
  3. Assign the List in the constructor line 130

These are the major changes we made. But we also actually need to call the method initPersonList() method somewhere in our app, before we actually start to look up the List for usage.

Comment

Add Reviews