Flutter bloc chat is not refreshing after sending message
Asked Answered
G

1

5

I'm new to bloc and trying to implement a chat with flutter_bloc package. The service for my messaging is twilio conversations api. My function works perfectly fine, Im simply not able to refresh my list of messages. Could somebody show me what I'm missing here? If I access the chatpage I can see all the messages, it only doesnt refresh if we have a new one.

I updated my code since I have a small success. Whenever User A or User B joins the chat, all messages are displayed. If I'm sending a message as User A, it will be visible in the UI for User A now and it is part of the conversation, BUT User B doesnt receive the new message which was added to the conversation without reloading. Which step is missing here so that the other User also receives the message? I just need help converting my code so I have a stream where the other participants of the chat can listen to so their conversation is refreshing too.

my chat_event.dart

  abstract class ChatEvent extends Equatable{
  const ChatEvent();

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

class InitialChatEvent extends ChatEvent {}

class AddMessage extends ChatEvent {
  final String messageToPost;

  AddMessage(this.messageToPost);
}

my chat_state.dart

   class ChatState extends Equatable {
  final Messages messages;

  const ChatState({required this.messages});

  factory ChatState.initial() =>  ChatState(messages: Messages(messages: []));

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

  @override
  bool get stringify => true;

  ChatState copyWith({
    List<Messages>? messages,
  }) {
    return ChatState(
      messages: this.messages,
    );
  }
}

part of chatpage

...
Expanded(
              child: BlocBuilder<ChatBloc, ChatState>(
                builder: (context, state) {
                  print('chatpage builder: ' + state.messages.toString());
                  return ListView.builder(
                      itemCount: state.messages.messages.length,
                      scrollDirection: Axis.vertical,
                      itemBuilder: (context, i) {
                        return ListTile(
                       tileColor: state.messages.messages[i].author.toString() == username ? Colors.amber : Colors.amber.shade100,
                      title: Text(
                        state.messages.messages[i].body.toString(),
                        style: TextStyle(color: Colors.black),
                      ),
                    );
                      });
                },
              ),
            ),
            ...
                  Container(
                      height: 50,
                      padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
                      child: RaisedButton(
                        textColor: Colors.white,
                        color: Colors.red,
                        child: Text('Button'),
                        onPressed: () async {
                          // print(chatMessage.text);
                          context.read<ChatBloc>().add(AddMessage(chatMessage.text));
                        },
                      )),
                ],
...

chat_bloc.dart

class ChatBloc extends Bloc<ChatEvent, ChatState> {
 
  ChatBloc() : super(ChatState.initial()) {
    //  print('wird ausgeführt');
    on<InitialChatEvent>((event, emit) async {
      final chatFeed = await HttpService().getMessages();
      emit(ChatState(messages: chatFeed));
    });
    
    on<AddMessage>((event, emit) async {
      final newConversation = await HttpService().postMessage(event.messageToPost);
      final chatFeed = await HttpService().getMessages();
      emit(ChatState(messages: chatFeed));
    });
  }
}

main.dart if needed

...

void main() => runApp(MultiBlocProvider(
        providers: [
          BlocProvider(create: (context) => ColorBloc()),
         BlocProvider(create: (context) => ChatBloc()),
        ],
        child: MaterialApp(
          title: "App",
          home: MyApp(),
        )));

class MyApp extends StatefulWidget {
  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  // This widget is the root of your application.
  TextEditingController nameController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          ...
              child: RaisedButton(
                textColor: Colors.white,
                color: Colors.red,
                child: Text('Button'),
                onPressed: () {
              print(nameController.text);
              context.read<ChatBloc>().add(InitialChatEvent());
              Navigator.of(context).push(
                MaterialPageRoute(
                  builder: (context) => ChatPage(userText: nameController.text)
                ),
              );
            },
              )),
        ],
      ),
    );
  }
}

http_service.dart

  Future postMessage(String messageToPost) async {
    Map<String, String> _messageToPost = {
      'Author': 'User A',
      'Body': messageToPost,
    };

    try {
      // print(messageToPost);

      var response = await dio.post(
          "https://conversations.twilio.com/v1/Conversations/$sid/Messages",
          data: _messageToPost,
          options: Options(
            contentType: Headers.formUrlEncodedContentType,
            headers: <String, String>{'authorization': basicAuth},
          ));

      return messageToPost;
    } catch (e) {
      print('exception: ' + e.toString());
      Future.error(e.toString());
    }
  }

  Future getMessages() async {
    try {
      final response = await dio.get(
          "https://conversations.twilio.com/v1/Conversations/$sid/Messages",
          // data: _messageToPost,
          options: Options(
            // contentType: Headers.formUrlEncodedContentType,
            headers: <String, String>{'authorization': basicAuth},
          ));
print(response.data);
      final messageList = Messages.fromJson(response.data);
      print(messageList);
      return messageList;
    } catch (e) {
      print('exception: ' + e.toString());
      return Future.error(e.toString());
    }
  }
}
Generalize answered 22/6, 2022 at 13:2 Comment(6)
There may be a problem with the HttpService().postMessage() method.Leucoplast
So the problem is that whenever user A sends a message, user B doesn't know that a message has been sent, so they don't update their UI, you need a way to let every user know at the same time that they have to update their UI, I don't know what technology you are using so I don't know how to do this, so please look into your technology's documentation for subscribing to changes or message stream or something like thatSeasonal
Thank you for your reply. I’m using twilio Conversation api. I actually don’t know which steps to do to change my code from list of messages to stream of message. Could you guide me regarding to my code? I could then try to find out by myself how to change my add and receive message function to get a stream backGeneralize
Here you are only pulling from Twilio and you are not listening for events that are coming in (e.g someone else is sending...). So, you need to handle real-time communication between the client and the server.... something like this: twilio.com/blog/2015/10/…... but for dart ;) From a personal perspective, I usually implement textual chats using a node socket server and front-end dart socket_io_client, under nginex.Theran
thank you for your reply. would you maybe provide me a code snippet for this? im not familiar with socket and real time communication at all..Generalize
If it is too complex providing an example with twilio you could also show me how to do it with socket_io_client, im not restricted to the twilio solution. It only would be nice if you could provide me the implementation using flutter_blocGeneralize
T
4

There is no short answer to this question, and if I have to write the full process here it's going to be too lengthy and redundant. Others have already written full tutorials on this like this fantastic one: https://blog.codemagic.io/flutter-ui-socket/. However, this might miss the BLoC part that you are interested in. So, below I will describe how you can use the example provided in this link and improve it with BLoC.

You will need to create a ChatBloc and a Socket. I prefer to create a Socket Singletone to make sure I have only a single socket connection at all times throughout the lifecycle of my apps :D.

Side Note
I acknowledge that there might be other ways of doing this. Here I am explaining my way ;) I have revolved the below example based on the written tutorial linked above for simplicity ;)

Chat BLoc

Will contains the basic message events:

  • SendMessage
  • GetMessage
  • LoadHistory
  • UpdateConnectedUsers

Here I will focus on the most sensitive one (i.e. Your concern). That is, GetMessage. The secret here is to call this event, not from the UI, but rather from the Socket Singletone. In other words, your socket will listen for incoming messages, and whenever it receives one, it triggers the 'GetMessage' event of the ChatBloc which, in turn, will update the UI. Here is an implementation attempt:

Socket Singletone

class SocketApi {
  IO.Socket _socket;
  //You need to inject an instance of the ChatBloc here so that you can 
  //call the 'GetMessage' event --- see comment below ;)
  ChatBloc chatBloc; 

  /// We are creating a singleton here to make sure we have only one instance 
  /// of Io.io at all time 
  static final SocketApi _socketApi = SocketApi._internal();
  SocketApi._internal() {
    _socket = IO.io(<url_to_your_socket_Server>, <String, dynamic>{
      'transports': ['websocket'],
      'autoConnect': false,
    });

    // This is where the magic happens... 
    // you listen of incoming messages 
    // on the socket and then trigger the chatBloc event 'GetMessage'. 
    _socket.on('message', (data) {
      // I am converting here to a chatMessage object... 
      // on your side you might have something like 'fromJson',
      // etc..
      ChatMessage chatMessage = chatMessageFromMap(data);
      chatBloc.add(GetMessage(chatMessage));
    });
  }
  //...
}

Now all that is left is to add a BlocBuilder or BlocSelector where ever you need your messages to be matched in your widget tree.

Final Important Note If you are really serious about using BLoC, I will recommend you check on the Freezed package that goes hand-in-hand with the bloc library. It helps you build your models quickly and efficiently. I have nothing to do with the developers of this package and I gain nothing by advertising. It just makes my life easier and I absolutely love the way it improves my code quality and wanted to share <3

Cheers and good luck!

Theran answered 29/6, 2022 at 19:24 Comment(10)
Thank you for your detailed answer. I’ll give it a try and will reply if I’m done implementing it. In my app I’m using online multiple 1:1 chats. Is this possible by using the socket as singleton?Generalize
Absolutely! anyways you always want to have only one connection then you stream as many messages as you want. Of course, don't forget to close it once done with it.Theran
ill update my code if im done so you can proof itGeneralize
is there something more efficient and less complex than bloc?Generalize
stick with BLoC... it's the easiest IMOTheran
alright. i tried my first state management solutions with provider package, but if the app went complex i noticed a lot performance issues, it seems that you dont have that using bloc since you have a business logic. ill go back with my flutter code as mvp so you can check the code especially the bloc part if its fineGeneralize
Share a GitHub link whenever you're ready ;)Theran
yes sure :) for the converting part here ChatMessage chatMessage = chatMessageFromMap(data); I get an error A value of type 'Message' can't be assigned to a variable of type 'List<Message>'. What do I have to configure in my model to convert in a list of models?Generalize
Let us continue this discussion in chat.Generalize
i posted the repository and further informations in the chat. Hoping to hear from you soon :)Generalize

© 2022 - 2024 — McMap. All rights reserved.