Search code examples
flutterbloc

Bloc UI not changing when an object in list is updated


I have a Todo model. For this I have a working version and not working version of the code snippets. I am trying to understand why the code doesn't work in the non working version where I try to put the logic inside the event handler for updating the UI when the checkbox is clicked. I can see the value changes from true to false and vice-versa . But the UI doesnt update. I create a new templist as well . So in theory , shouldn't it emit the changes to the UI ?

ToDo Model class :

import 'dart:convert';

class ToDo {
  final String title;
  final String description;
  bool isDone;
  ToDo({
    required this.title,
    required this.description,
    required this.isDone,
  });

  ToDo copyWith({
    String? title,
    String? description,
    bool? isDone,
  }) {
    return ToDo(
      title: title ?? this.title,
      description: description ?? this.description,
      isDone: isDone ?? this.isDone,
    );
  }

  Map<String, dynamic> toMap() {
    return <String, dynamic>{
      'title': title,
      'description': description,
      'isDone': isDone,
    };
  }


  @override
  bool operator ==(covariant ToDo other) {
    if (identical(this, other)) return true;

    return other.title == title &&
        other.description == description &&
        other.isDone == isDone;
  }

  @override
  int get hashCode => title.hashCode ^ description.hashCode ^ isDone.hashCode;
}

Working Code :

In blocBuilder :

Checkbox(
                                value: state.todoList[index].isDone,
                                onChanged: (value) {
                                  context.read<TodoBloc>().add(UpdateToDo(
                                      todo: state.todoList[index].copyWith(
                                          isDone:
                                              !state.todoList[index].isDone),
                                      // todo: state.todoList[index],
                                      index: index));
                                },
                              ),

code in EventHandler :

void _updateToDo(
    UpdateToDo event,
    Emitter<TodoState> emit,
  ) {
    // emit(TodoLoading());
    try {
      final state = this.state;

      if (state is TodoLoaded) {
        List<ToDo> tasks = (state.todoList.map((task) {
          return task.title == event.todo.title ? event.todo : task;
        })).toList();
        emit(TodoLoaded(todoList: tasks));
      }
    } catch (e) {
      emit(TodoError(error: e.toString()));
    }
  }

Not working code :

In blocBuilder :

Checkbox(
                                value: state.todoList[index].isDone,
                                onChanged: (value) {
                                  context.read<TodoBloc>().add(UpdateToDo(
                                      
                                      todo: state.todoList[index],
                                      index: index));
                                },
                              ),

code in Event Handler :

void _updateToDo(
    UpdateToDo event,
    Emitter<TodoState> emit,
  ) {
    // emit(TodoLoading());
    try {
      final state = this.state;
      if (state is TodoLoaded) {
        List<ToDo> tempList = List.from(state.todoList);
        for (int index = 0; index < tempList.length; index++) {
          if (index == event.index) {
            tempList[index].isDone = !tempList[index].isDone;
          }
        }
        emit(TodoLoaded(todoList: tempList));
      }
    } catch (e) {
      emit(TodoError(error: e.toString()));
    }
  }

Solution

  • In the Block concept, the UI reacts to changes that happen in the states, the states are changed by emitting a new state in the event. If you have an event that doesn't change the state, from the UI's perception nothing has changed, even if the parameters that the states return change, the state is still the same, so nothing has changed from the UI's perspective.

    So, your CheckBox doesn't change because your UI starts the event with the state TodoLoaded and ends with the same state, so nothing has changed.

    As you probably already know, you can solve this by uncommenting the emit you have at the beginning of your function, the function would look like this:

    void _updateToDo(
     UpdateToDo event,
     Emitter<TodoState> emit,
    ) {
      emit(TodoLoading());
      try {
       final state = this.state;
       if (state is TodoLoaded) {
         List<ToDo> tempList = List.from(state.todoList);
         for (int index = 0; index < tempList.length; index++) {
           if (index == event.index) {
             tempList[index].isDone = !tempList[index].isDone;
           }
         }
         emit(TodoLoaded(todoList: tempList));
       }
      } catch (e) {
       emit(TodoError(error: e.toString()));
      }
    }
    

    This will work because your UI will recognize the changes from TodoLoaded state, then TodoLoading state and finally the TodoLoaded state again with the new data.

    Hope this help you understand.