Ajit Kumar
Ajit Kumar

Reputation: 417

Obx ListView not updating after value changes

I am having a RxList called todoData. On the basis of this list, a ListView.builder builds a list and in each list there is a button. When this button is clicked the done field of the respective item is updated to either true or false. But, though the value is updated the ui is no changing.
Here's the list:

class StateController extends GetxController {
  RxList<Todo> todoData = <Todo>[
Todo(
    name: 'todo1',
    done: true),
Todo(
    name: 'todo2',
    done: false),
Todo(
    name: 'todo3',
    done: true),
Todo(
    name: 'todo4',
    done: false),
Todo(
    name: 'todo5',
    done: false)
 ].obs;
}

Controller:

  final StateController _controller = Get.find();

The update function:

void updateItem(Todo e) {
  /* final int index = _controller.todoData.indexOf(e);
  _controller.todoData[index].done = !e.done; */
  _controller.todoData.firstWhere((Todo i) => i == e).done = !e.done;
  _controller.refresh();
}

void deleteItem(Todo e) {     //**this works**
  final int index = _controller.todoData.indexOf(e);
  _controller.todoData.removeAt(index);
}

Ui:

Obx(() => ListView.builder(
                          itemCount: _controller.todoData.length,
                          itemBuilder: (_, int i) => TodoItem(
                              item: _controller.todoData[i],
                              updateItem: () =>
                                  updateItem(_controller.todoData[i]),
                        ))

Any help is greatly appreciated!

Upvotes: 4

Views: 2064

Answers (4)

Bruno Kinast
Bruno Kinast

Reputation: 1168

Obx only reacts to changes made in reactive (Rx) variables. The only reactive variable you have currently is the RxList, so only changes made to that list will trigger your Obx to update.

When you change done, you're changing a value in Todo, not the list, so no update is made to the UI.

One way to fix this is to force an update in the list when you change done, you can do this by calling refresh() on the RxList:

void updateItem(Todo e) {
  _controller.todoData.firstWhere((Todo i) => i == e).done = !e.done;
  _controller.todoData.refresh(); // <- Here
}

(Also, assuming e is the same object contained in the list, you can do e.done = !e.done directly)

Upvotes: 7

jawad111
jawad111

Reputation: 41

Try Calling the Update Function inside

WidgetsBinding.instance.addPostFrameCallback((_) {
            // Update Code Goes Here //
 });

The update may not happen when Obx is already In process of Building. The addPostFrameCallback will execute after the current build has finished and it will allow the UI to be updated.

Upvotes: 1

Hamza Siddiqui
Hamza Siddiqui

Reputation: 716

i just modify some code of yours, just change the RxList to simple List and bool variable to RxBool and use obx on bool I'm sharing code

Your Model class and controller class

    class StateController extends GetxController {
      List<Todo> todoData = <Todo>[
        Todo(name: 'todo1', done: true.obs),
        Todo(name: 'todo2', done: false.obs),
        Todo(name: 'todo3', done: true.obs),
        Todo(name: 'todo4', done: false.obs),
        Todo(name: 'todo5', done: false.obs)
      ].obs;
    }
    
    class Todo {
      String? name;
      RxBool? done;
      Todo({this.name, this.done});
    }

your UI Code

    class TestClass extends StatelessWidget {
      TestClass({Key? key}) : super(key: key);
      final StateController _controller = Get.put(StateController());
    
      void updateItem(Todo e) {
        /* final int index = _controller.todoData.indexOf(e);
      _controller.todoData[index].done = !e.done; */
        _controller.todoData.firstWhere((Todo i) => i == e).done!.value =
            !e.done!.value;
        _controller.refresh();
      }
    
      void deleteItem(Todo e) {
        //**this works**
        final int index = _controller.todoData.indexOf(e);
        _controller.todoData.removeAt(index);
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
            backgroundColor: Colors.white,
            body: ListView.builder(
                itemCount: _controller.todoData.length,
                itemBuilder: (_, int i) => TodoItem(
                      item: _controller.todoData[i],
                      updateItem: () => updateItem(_controller.todoData[i]),
                    )));
      }
    }
    
    class TodoItem extends StatelessWidget {
      TodoItem({Key? key, this.item, this.updateItem}) : super(key: key);
      Todo? item;
      VoidCallback? updateItem;
    
      @override
      Widget build(BuildContext context) {
        return ListTile(
          title: Text(
            item!.name!,
            style: TextStyle(color: Colors.black),
          ),
          subtitle: GestureDetector(
            onTap: updateItem,
            child: Obx(() {
              return Text(
                item!.done!.value.toString(),
                style: TextStyle(color: Colors.black),
              );
            }),
          ),
        );
      }
    }

Upvotes: 3

Carter McKay
Carter McKay

Reputation: 490

There are a few things you can do here to make your code work as expected.

First, you need to make sure that your Todo class implements the Equatable mixin. This will allow you to compare two Todo instances by value rather than by reference.

Second, you need to use the update() method on your RxList to update the value of the done field for the appropriate Todo instance.

Third, you need to call the refresh() method on your StateController after updating the done field so that the UI will be updated to reflect the new value.

Fourth, you should consider using the TodoItem widget's built-in updateItem() method rather than passing a callback to the widget. This will ensure that the TodoItem widget is updated correctly when the done field is changed.

Upvotes: 1

Related Questions