5

I am using bloc in flutter app and my problem is view is not update when yield new state. I want to add new message to my list of messages. and I yield same state with new data but my view doesn't update after adding new message to list. Bloc:

class ChatBloc extends Bloc<ChatEvent, ChatState> {
  @override
  ChatState get initialState => ChatInitial();

  @override
  Stream<ChatState> mapEventToState(ChatEvent event) async* {
   if (event is AddToMessages) {
      yield* _mapAddToMessagesEventToState(event.chatMessage);
    }
  }

  Stream<ChatState> _mapAddToMessagesEventToState(ChatMessage chatMessage) async* {
    List<ChatMessage> chatMessages = (state as DataLoaded).chatMessages;
    chatMessages.insert(0, chatMessage);
    yield DataLoaded(chatMessages: chatMessages);
  }
}

event:

abstract class ChatEvent extends Equatable {}
class AddToMessages extends ChatEvent {
  final ChatMessage chatMessage;
  AddToMessages({@required this.chatMessage});
  @override
  List<Object> get props => [chatMessage];
}

state:

abstract class ChatState extends Equatable {
  const ChatState();
}

class ChatInitial extends ChatState {
  const ChatInitial();

  @override
  List<Object> get props => [];
}
class DataLoaded extends ChatState {
  final List<ChatMessage> chatMessages;
  const DataLoaded({this.chatMessages});

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

and View:

class ChatScreen extends StatelessWidget {
  Widget createBody(ChatState state, BuildContext context) {
    if (state is DataLoaded) {
      return Column(
        children: <Widget>[
          MessagesList(
            messages: state.chatMessages,
          ),
          SendBar(
            onMessage: (message) => BlocProvider.of<ChatBloc>(context).add(
              AddToMessages(
                chatMessage: ChatMessage(
                  chatMessageType: ChatMessageType.TEXT,
                  dateTime: DateTime.now(),
                  message: message,
                  userId: store.state.userProfile.id,
                ),
              ),
            ),
          ),
        ],
      );
    } else {
      return Container();
    }
  }

  @override
  Widget build(BuildContext context) {
    return BlocProvider<ChatBloc>(
      create: (context) {
        return ChatBloc(chatRepository: chatRepository)..add(DataLoaded(chatMessages: []));
      },
      child: BlocListener<ChatBloc, ChatState>(
        listener: (context, state) async {},
        child: BlocBuilder<ChatBloc, ChatState>(
          builder: (context, state) {
            return Scaffold(
              body: createBody(state, context),
            );
          },
        ),
      ),
    );
  }
}

When I add new message, view doesn't update. Where is my mistake.

BeHappy
  • 3,705
  • 5
  • 18
  • 59

2 Answers2

4

Edit: turns out after all that because you are using equatable, and because when you emit a new state, you are only changing the list of the old state by inserting a message into it, then emitting the old state with the same list,so when bloc compares the two states, and because the state class extends equatable, the bloc considers them the same and doesn't emit a state

solution: remove the equatable

or

use immutable lists so you can emit a new state that has a different property than the previous one and can be distinguished by bloc

HII
  • 3,420
  • 1
  • 14
  • 35
  • So, I have to remove `const` in my state class? – BeHappy Apr 21 '20 at 21:50
  • you can keep it if you are calling it from somewhere else(which is what I doubt), but anyway from the mapEventToState() you shouldn't call it especially with using equatable, so adding a non-const constructor must solve your issue,see the link in the answer – HII Apr 21 '20 at 21:52
  • I don't want to yield other state. – BeHappy Apr 21 '20 at 22:47
  • did you try removing const constructors and it didn't work? sorry but I can't try it for you as I don't have your code – HII Apr 22 '20 at 04:51
  • I define like this `DataLoaded({this.chatMessages});` – BeHappy Apr 22 '20 at 19:15
  • and also without `Equatable` works. But how this work with it? – BeHappy Apr 22 '20 at 19:15
  • I don't understand what you did, and I don't understand if it worked or not ( the language is not clear) – HII Apr 22 '20 at 19:21
  • I don't understand what you mean by non-const constructor, I just change `const DataLoaded({this.chatMessages});` to `DataLoaded({this.chatMessages});` – BeHappy Apr 22 '20 at 19:28
  • what you did is what I mean, you were returning the same instance of state to the bloc that's why it was not emitting it – HII Apr 22 '20 at 19:30
  • @BeHappy have a look https://stackoverflow.com/q/21744677/9142279 – HII Apr 22 '20 at 19:32
  • That change doesn't resolve problem. removing `Equatable` works. – BeHappy Apr 22 '20 at 19:32
  • ah ok, but the reasoning is still the same, the bloc was considering the next state to be same as previous one because you are returning the same list of messages each time and using equatable (which in turn will mark the two states as equal when they are compared because you are not using immutable lists but you are keeping the same list and just inserting into it) – HII Apr 22 '20 at 19:40
  • I follow todo list in bloc docs. I think we have same approach, but for mine not work. – BeHappy Apr 22 '20 at 19:42
0

List of something is not working properly with Equatable. It always returns true even if List elements are not same.

So I have a workaround for this. I added another List property and a bool property to help people who has lots of properties in State of Bloc.

class DataLoaded extends ChatState {
  final List<Member> roomMembers;
  final List<ChatMessage> chatMessages;
  final bool isOpen;

  
  final List<Object> allElements;

  DataLoaded({this.chatMessages, this.roomMembers, this.isOpen}) 
    : allElements = [...chatMessages, ...roomMembers, isOpen];

  @override
  List<Object> get props => allElements;
}

I hope it will help you, it works on me :)

akaymu
  • 31
  • 4