flutterpusherflutter-blocflutter-cubit

in flutter, i have used flutter_bloc cupit with pusher, but when getting new data ui not updated


this is my bloc code

i want to create functionality of real time chat with Laravel API, pusher, and with one signal. All of my code works correctly except when i send new message to chat user, user receives my message and I'm able to print the message and data but recipient user's UI is not updating for new message.

            import 'package:flutter/material.dart';
            import 'package:soulsphere/bloc/chat/chat_message_state.dart';
            import 'package:flutter_bloc/flutter_bloc.dart';
            import 'package:soulsphere/repositories/chats/chat_messages_repo.dart';
            import 'package:soulsphere/model/chat_message.dart';
            
            class ChatMessageCubit extends Cubit<ChatMessageState>{
              final BuildContext context;
              final String chatUserID;
            
              ChatMessageCubit({
                required this.context,
                required this.chatUserID,
              }) : super(ChatMessageLoadingState()){
                fetchChatMessage(context, chatUserID);
              }
            
              ChatMessageRepository chatMessageRepository = ChatMessageRepository();
            
              void fetchChatMessage(BuildContext context, String? chatUserID) async{
                print("========================================");
                print("in fetch chat message api");
                  try{
                    print("fetch message for: $chatUserID");
                    if(chatUserID != 0){
                      List<ChatMessage> chatMessages = await chatMessageRepository.fetchChatsFromApi(context, chatUserID);
                      emit(ChatMessageLoadedState(chatMessages));
                    }
                    else{
                      emit(ChatMessageErrorState("No Chat User Found"));
                    }
                  }
                  catch (ex){
                    emit(ChatMessageErrorState(ex.toString()));
                  }
              }
            
              void sendMessage(BuildContext context, String message, String chatUserID) async {
                try{
                  List<ChatMessage> sentMessage = (await chatMessageRepository.sendMessage(context, message, chatUserID)).cast<ChatMessage>();
                  emit(ChatMessageLoadedState(sentMessage));
                }
                catch (ex){
                  emit(ChatMessageErrorState(ex.toString()));
                }
              }
            
              void addNewMessageFromPusher(Map<String, dynamic> messageData) {
                print("in add new message from pusher");
                if (state is ChatMessageLoadedState) {
                  ChatMessage newMessage = ChatMessage.fromJson(messageData);
                  List<ChatMessage> currentMessages = List.from((state as ChatMessageLoadedState).chatMessages);
                  currentMessages.add(newMessage);
                  print("============================================================");
                  print("============================================================");
                  emit(ChatMessageLoadedState(currentMessages));
                }
              }
            
            
            }

and my chat screen code

        import 'package:flutter/material.dart';
import 'package:pusher_client/pusher_client.dart';
import 'package:soulsphere/controller/laravel_echo/laravel_echo.dart';
import 'package:soulsphere/screens/users/user_view.dart';
import 'package:soulsphere/utils/app_constants.dart';
import 'package:soulsphere/model/user.dart';
import 'package:soulsphere/model/chat_message.dart';
import 'package:shimmer/shimmer.dart';
import 'dart:convert';
import 'package:soulsphere/utils/show_toast.dart';
import 'package:soulsphere/utils/shared_pref.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:soulsphere/bloc/chat/chat_message_cubit.dart';
import 'package:soulsphere/bloc/chat/chat_message_state.dart';


class ChatDetailsScreen extends StatefulWidget {
  final User user;

  const ChatDetailsScreen({
    Key? key,
    required this.user,
  }) : super(key: key);

  @override
  State<ChatDetailsScreen> createState() => _ChatDetailsScreenState();
}

class _ChatDetailsScreenState extends State<ChatDetailsScreen> {
  bool isLoading = true;
  late String userID;
  bool isChatDataFetched = true;
  late ChatMessageCubit chatMessageCubit;

  void listenChatChannel(chatID) {
    LaravelEcho.instance.channel('chat.$chatID').listen(
      '.message.sent',
          (e) {
        if (e is PusherEvent) {
          if (e.data != null) {
            Map<String, dynamic> messageData = json.decode(e.data!);
            print("in function listen channel");
            print(messageData);
            chatMessageCubit.addNewMessageFromPusher(messageData);
          }
        }
      },
    ).error((err) {
      showToast(context, 'Pusher error: $err');
    });
  }

  void leaveChatChannel(chatID){
      try{
          LaravelEcho.instance.leave('chat.$chatID');
      }
      catch(err){
        showToast(context, err.toString());
      }
  }

  @override
  void initState() {
    super.initState();
    getUserDataFromSharedPreferences();
    String chatID = widget.user.chatID.toString();

    // Initialize chatMessageCubit before using it in listenChatChannel
    chatMessageCubit = ChatMessageCubit(context: context, chatUserID: widget.user.userID.toString());

    listenChatChannel(chatID);
  }

  @override
  void dispose() {
    String chatID = widget.user.chatID.toString();
    leaveChatChannel(chatID);
    chatMessageCubit.close();
    super.dispose();
  }

  // ------- make UserID variable for using in build ------- //
  Future<void> getUserDataFromSharedPreferences() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    setState(() {
      userID = prefs.getString(CustomSharedConstants.userID) ?? '';
    });
  }


  @override
  Widget build(BuildContext context) {
    User user = widget.user;

    return BlocProvider(
      create: (context) => ChatMessageCubit(context: context, chatUserID: widget.user.userID.toString()),
      child: Scaffold(
        appBar: AppBar(
          title: Text(user.userName ?? AppConstants.noUserName),
          backgroundColor: Colors.transparent,
          flexibleSpace: Container(
            decoration: const BoxDecoration(
              gradient: AppColors.bgColorGradient,
            ),
          ),
          actions: [
            PopupMenuButton(
              itemBuilder: (context) {
                return [
                  const PopupMenuItem(
                    value: 'profile',
                    child: Text('View Profile'),
                  ),
                  const PopupMenuItem(
                    value: 'settings',
                    child: Text('Settings'),
                  ),
                ];
              },
              onSelected: (value){
                if(value == 'profile'){
                  Navigator.push(context, MaterialPageRoute(builder: (context) => UserProfileScreen(user: user)));
                }
              },
            ),
          ],
        ),
        body: BlocBuilder<ChatMessageCubit, ChatMessageState>(
          key: const Key('chatMessagesKey'),
          builder: (context, state) {
            if(state is ChatMessageLoadingState){
              print("in loading state");
              return const Center(child: CircularProgressIndicator());
            }
            else if (state is ChatMessageLoadedState){
              print("in loaded state");
              return Column(
                children: [
                  Expanded(
                    child: SingleChildScrollView(
                      child: ChatMessages(messages: state.chatMessages, userId: userID),
                    ),
                  ),
                  ChatInputField(
                    onSendMessage: (message, chatUserID) {
                      context.read<ChatMessageCubit>().sendMessage(context, message, chatUserID);
                    },
                    chatUserID: widget.user.userID.toString(),
                    onUpdateChatMessages: (updatedMessages) {
                      // Implement the logic to update messages if needed
                    },
                  ),
                ],
              );
            }
            else if(state is ChatMessageErrorState){
              return Center(child: Text('Error: ${state.error}'));
            }
            else{
              return const Center(child: Text('Failed to load data from API'));
            }
          },
        ),
      ),
    );
  }
}

class ShimmerEffect extends StatelessWidget {
  const ShimmerEffect({super.key});

  @override
  Widget build(BuildContext context) {
    return Shimmer.fromColors(
      baseColor: Colors.grey[300]!,
      highlightColor: Colors.grey[100]!,
      child: ListView.builder(
        shrinkWrap: true,
        physics: const NeverScrollableScrollPhysics(),
        itemCount: 10,
        itemBuilder: (BuildContext context, int index) {
          return ListTile(
            title: Container(
              width: 200.0,
              height: 20.0,
              color: Colors.white,
            ),
          );
        },
      ),
    );
  }
}

class ChatMessages extends StatelessWidget {
  final List<ChatMessage> messages;
  final String userId;

  const ChatMessages({
    Key? key,
    required this.messages,
    required this.userId,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      shrinkWrap: true,
      physics: const NeverScrollableScrollPhysics(),
      itemCount: messages.length,
      itemBuilder: (BuildContext context, int index) {
        return ChatBubble(
          message: messages[index].messageText!,
          isMe: messages[index].userID.toString() == userId,
        );
      },
    );
  }
}

class ChatBubble extends StatelessWidget {
  final String message;
  final bool isMe;

  const ChatBubble({
    Key? key,
    required this.message,
    required this.isMe,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Align(
      alignment: isMe ? Alignment.centerRight : Alignment.centerLeft,
      child: Container(
        margin: const EdgeInsets.all(8.0),
        padding: const EdgeInsets.all(12.0),
        decoration: BoxDecoration(
          color: isMe ? Colors.blue : Colors.grey[300],
          borderRadius: BorderRadius.circular(10.0),
        ),
        child: Text(
          message,
          style: TextStyle(color: isMe ? Colors.white : Colors.black),
        ),
      ),
    );
  }
}

class ChatInputField extends StatefulWidget {
  final Function(String, String) onSendMessage;
  final String chatUserID;
  final Function(List<ChatMessage>) onUpdateChatMessages;

  const ChatInputField({
    Key? key,
    required this.onSendMessage,
    required this.chatUserID,
    required this.onUpdateChatMessages,
  }) : super(key: key);

  @override
  State<ChatInputField> createState() => _ChatInputFieldState();
}

class _ChatInputFieldState extends State<ChatInputField> {
  bool _isSending = false;
  final _messageController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white,
      padding: const EdgeInsets.all(8.0),
      child: Row(
        children: [
          IconButton(
            icon: const Icon(Icons.attach_file),
            onPressed: () {
              // Handle attachment button press
            },
          ),
          Expanded(
            child: TextFormField(
              controller: _messageController,
              decoration: const InputDecoration(
                contentPadding: EdgeInsets.all(15.0),
                hintText: 'Type a message...',
                border: InputBorder.none,
              ),
            ),
          ),
          IconButton(
            icon: _isSending
                ? const CircularProgressIndicator(
              valueColor: AlwaysStoppedAnimation<Color>(Colors.blue),
            )
                : const Icon(Icons.send),
            onPressed: _isSending
                ? null
                : () async {
                      setState(() {
                        _isSending = true;
                      });
                      String message = _messageController.text;
                      await widget.onSendMessage(message, widget.chatUserID);
                      setState(() {
                        _isSending = false;
                        _messageController.clear();
                      });
                    },
          ),
        ],
      ),
    );
  }
}


i have tried with changes a lot in codes.

                void listenChatChannel(chatID) {
                  LaravelEcho.instance.channel('chat.$chatID').listen(
                    '.message.sent',
                        (e) {
                      if (e is PusherEvent) {
                        if (e.data != null) {
                          Map<String, dynamic> messageData = json.decode(e.data!);
                          print("in function listen channel");
                          print(messageData);
                          chatMessageCubit.addNewMessageFromPusher(messageData);
                        }
                      }
                    },
                  ).error((err) {
                    showToast(context, 'Pusher error: $err');
                  });
                }

this code is triggered when new message received by target user


Solution

  • You are using different instances of ChatMessageCubit.you can pass the same instance of ChatMessageCubit to all the BlocBuilders. You are creating a chatMessageCubit instance in you initState.just pass the same instance to the BlocBuilders. sample code is given below.

    BlocProvider(
          create: (context) => chatMessageCubit,
          child: Scaffold(
            appBar: AppBar(
              title: Text(user.userName ?? AppConstants.noUserName),
              backgroundColor: Colors.transparent,
              flexibleSpace: Container(
                decoration: const BoxDecoration(
                  gradient: AppColors.bgColorGradient,
                ),
              ),