List tile button changes state after pressing another button

Issue

There is a drawer with list tiles containing check all and uncheck all buttons, When I click on the check all button it should check the checkbox of all the list items but the UI changes or updates only after I click any other button, the uncheck button also produces the same issue.

drawer: Drawer(
    child: Obx(() => ListView(
          padding: EdgeInsets.zero,
          children: <Widget>[
            UserAccountsDrawerHeader(
              decoration: BoxDecoration(
                color: (Get.isDarkMode) ? Colors.black26 : Colors.grey[300],
              ),
              accountName: Text('Reminders',
                  style: TextStyle(
                    fontSize: 30.0,
                    color: Theme.of(context).textTheme.headline1!.color,
                  )),
              currentAccountPicture: CircleAvatar(
                  backgroundColor: Colors.white,
                  child: Image(
                    image: AssetImage("assets/App_icon.png"),
                  )),
              accountEmail: null,
            ),
            ListTile(
              title: Text("Check all",
                  style: TextStyle(
                      fontSize: 17.0,
                      color: Theme.of(context).textTheme.headline1!.color)),
              leading: Icon(Icons.check_box),
              onTap: () {
                checkAll();
              },
            ),
            ListTile(
              title: Text("Uncheck all",
                  style: TextStyle(
                      fontSize: 17.0,
                      color: Theme.of(context).textTheme.headline1!.color)),
              leading: Icon(Icons.check_box_outline_blank),
              onTap: () {
                unCheckAll();
              },
            ),
            ListTile(
              title: Text(
                (Get.isDarkMode)
                    ? 'Change theme:  light'
                    : 'change theme:  dark',
                style: TextStyle(
                    fontSize: 17.0,
                    color: Theme.of(context).textTheme.headline1!.color),
              ),
              leading: Icon(Icons.color_lens_sharp),
              onTap: () {
                if (Get.isDarkMode) {
                  Get.changeThemeMode(ThemeMode.light);
                } else {
                  Get.changeThemeMode(ThemeMode.dark);
                }
              },
            ),
            ListTile(
              title: Text(
                "Delete all",
                style: TextStyle(
                    fontSize: 17.0,
                    color: Theme.of(context).textTheme.headline1!.color),
              ),
              leading: Icon(Icons.delete),
              onTap: () {
                todoController.todos.clear();
              },
            ),
            ListTile(
              title: Text(
                "No of tasks:  ${todoController.todos.length}",
                style: TextStyle(
                    fontSize: 20,
                    color: Theme.of(context).textTheme.headline1!.color),
              ),
              onTap: () {},
            ),
          ],
        )),

Check all and uncheck all function:

void checkAll() {
  TodoController todoController = Get.put(TodoController());
  for (var i = 0; i < todoController.todos.length; i++) {
  todoController.todos[i].done = true;
 }
 GetStorage().write('todos', todoController.todos.toList());
}

void unCheckAll() {
 TodoController todoController = Get.put(TodoController());
 for (var i = 0; i < todoController.todos.length; i++) {
   todoController.todos[i].done = false;
 }
 GetStorage().write('todos', todoController.todos.toList());
}

Solution

Although it may seem weird but doing this will solve it:

.... 
todoController.todos.assignAll(todoController.todos.toList());
GetStorage().write('todos', todoController.todos.toList());

Why?

Assuming the todoController.todos is a RxList, changing the properties of the individual elements of the list has no effect on the underlying stream. And therefore despite the values changed it will not trigger a rebuild on the Obx.

But doing something like todoController.todos.assignAll(todoController.todos.toList()); actually changes the underlying stream/value of the RxList. Thus triggers the rebuild as it should be.

When working with Rx or observables, always keep in mind that Obx/GetX will only, and only trigger rebuild if the actual value of the Rx/Observable is changed.
Something like todoController.todos[i].done = true; means changing the value of the done property of i th element of the RxList and not the value of the actual RxList. assignAll does that.

Same applies to other Rxs as well.
Suppose the following is a Rx object of the SomeModel class:

final someModel= SomeModel().obs;

Doing something like:

someModel.value.someProperty = someValue;

will not trigger the UI rebuild but doing something like this:

 someModel.value.someProperty = someValue;

  someModel.value = someModel.value;

will.

But it’s a bit weird right?
In that case something like copyWith utility method can be useful.

Answered By – S. M. JAHANGIR

Answer Checked By – Candace Johnson (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *