Max
Max

Reputation: 1301

Problem with events in BLoC Flutter. I always call two events instead of one

I return the state with the TodoLoadedState list in each block method. But when I call the block event in onPressed, the list itself is not returned and I have to add a second call to the block method todoBloc.add(LoadTodos()); But that's not correct. Ideas need to trigger 1 event but to perform 2 actions, the second action is to update the list. Thanks in advance!

todo_bloc

class TodoBloc extends Bloc<TodoEvent, TodoState> {
  final TodoRepository todoRepository;

  TodoBloc(this.todoRepository) : super(TodoEmptyState()) {
    on<LoadTodos>((event, emit) async {
      emit(TodoLoadingState());
      try {
        final List<Todo> _loadedTodoList = await todoRepository.getAllTodos();
        emit(TodoLoadedState(loadedUser: _loadedTodoList));
      } catch (_) {
        emit(TodoErrorState());
      }
    });
    on<CreateTodos>((event, emit) async {
      // Todo todo = Todo(description: event.task, isDone: false);
      await todoRepository.insertTodo(event.todo);
      final List<Todo> _loadedTodoList = await todoRepository.getAllTodos();
      emit(TodoLoadedState(loadedUser: _loadedTodoList));
    });
    on<DeleteTodos>((event, emit) async {
      await todoRepository.deleteTodo(event.id);
      final List<Todo> _loadedTodoList = await todoRepository.getAllTodos();
      emit(TodoLoadedState(loadedUser: _loadedTodoList));
    });
    on<UpdateTodos>((event, emit) async {
      await todoRepository.updateTodo(event.todo);
      final List<Todo> _loadedTodoList = await todoRepository.getAllTodos();
      emit(TodoLoadedState(loadedUser: _loadedTodoList));
    });
  }
}

todo_list

class TodoList extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final TodoBloc todoBloc = context.read<TodoBloc>();
    return BlocBuilder<TodoBloc, TodoState>(builder: (context, state) {
      if (state is TodoEmptyState) {
        return const Center(
          child: Text(
            'No Todo',
            style: TextStyle(fontSize: 20.0),
          ),
        );
      }

      if (state is TodoLoadingState) {
        return const Center(child: CircularProgressIndicator());
      }

      if (state is TodoLoadedState) {
        return ListView.builder(
            physics: const NeverScrollableScrollPhysics(),
            shrinkWrap: true,
            itemCount: state.loadedUser.length,
            itemBuilder: (context, index) => ListTile(
                  title: Column(children: [
                    Text('${state.loadedUser[index].description}'),
                    Text('${state.loadedUser[index].id}'),
                  ]),
                  trailing: IconButton(
                    onPressed: () {
                      todoBloc.add(DeleteTodos(id: state.loadedUser[index].id));
                      todoBloc.add(LoadTodos());
                    },

home_page

class HomePage extends StatelessWidget {
  final todoRepository = TodoRepository();

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<TodoBloc, TodoState>(builder: (context, state) {
      return Scaffold(
        appBar: AppBar(
          title: const Text('Flutter Todos'),
        ),
        body: SingleChildScrollView(
          child: Column(
            children: [
              TodoList(),
            ],
          ),
        ),
        floatingActionButton: FloatingActionButton(
          child: const Icon(Icons.add, size: 32, color: Colors.white),
          onPressed: () {
            final TodoBloc todoBloc = context.read<TodoBloc>();
            final _todoDescriptionFromController = TextEditingController();
            showModalBottomSheet(
                context: context,
                builder: (builder) {
                  return Padding(
                    padding: EdgeInsets.only(
                        bottom: MediaQuery.of(context).viewInsets.bottom),
                    child: Container(
                      color: Colors.transparent,
                      child: Container(

todo_state

abstract class TodoState extends Equatable {
  const TodoState();

  @override
  List<Object> get props => [];
}

class TodoLoadingState extends TodoState {}

class TodoEmptyState extends TodoState {}

class TodoLoadedState extends TodoState {
  List<dynamic> loadedUser;

  TodoLoadedState({required this.loadedUser});
}

class TodoErrorState extends TodoState {}

todo_event

abstract class TodoEvent extends Equatable {
  const TodoEvent();

  @override
  List<Object> get props => [];
}

class LoadTodos extends TodoEvent {}

class CreateTodos extends TodoEvent {
  final Todo todo;

  const CreateTodos(this.todo);
}

class UpdateTodos extends TodoEvent {
  final Todo todo;

  const UpdateTodos(this.todo);
}

class DeleteTodos extends TodoEvent {
  final int id;

  const DeleteTodos({required this.id});
}

class QueryTodo extends TodoEvent {}

event onPressed, everywhere you have to use 2 events to load the updated list

todoBloc.add(UpdateTodos(updateTodo));
todoBloc.add(LoadTodos());

Upvotes: 1

Views: 1564

Answers (1)

Ramin
Ramin

Reputation: 935

This is the culprit:

abstract class TodoState extends Equatable {
  const TodoState();

  @override
  List<Object> get props => [];
}

You are extending Equatable in TodoState and passing an empty list to props. When other states such as TodoLoadedState extend TodoState they inherit Equatable as well and the empty props.

If you're using Equatable make sure to pass all properties to the props getter.

This is from bloc faq. Right now all instances of your TodoLoadedState are considered equal. Doesn't matter if you have a TodoLoadedState with hundreds of loadedUser or a TodoLoadedState with none. They are both considered equal and only the first time you pass a new TodoLoadedState the BlocBuilder will update. The consequent ones have no effect since BlocBuilder thinks it is the same as previous one. The reason your LoadTodos event causes a rebuild is that first you emit TodoLoadingState() and then in case of success TodoLoadedState(loadedUser: _loadedTodoList). This alternating between two different states makes it work.

So either don't use Equatable or make sure to pass all the properties to props.

class TodoLoadedState extends TodoState {
  final List<dynamic> loadedUser;

  TodoLoadedState({required this.loadedUser});
  @override
  List<Object?> get props => [loadedUser];
}

Upvotes: 4

Related Questions