在 Flutter 中,我使用了带有 pusher flutter_bloc cupit,但是在获取新数据时 ui 没有更新

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

提问人:yogeshdinodia 提问时间:11/10/2023 最后编辑:yogeshdinodia 更新时间:11/11/2023 访问量:46

问:

这是我的 bloc 代码

我想使用 Laravel API、推送器和一个信号创建实时聊天功能。我的所有代码都正常工作,除非我向聊天用户发送新消息,用户收到我的消息,并且我能够打印消息和数据,但收件人用户的 UI 没有更新新消息。

            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));
                }
              }
            
            
            }

和我的聊天屏幕代码

        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();
                      });
                    },
          ),
        ],
      ),
    );
  }
}


我已经尝试了很多代码更改。

                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');
                  });
                }

当目标用户收到新消息时触发此代码

推杆 颤振-bloc 颤振肘

评论


答:

1赞 SaifAlmajd 11/10/2023 #1

你可以使用 flutter streambuilder 你可以在这里看到更多 (https://api.flutter.dev/flutter/widgets/StreamBuilder-class.html)

StreamBuilder 的示例

StreamBuilder<String>(
stream: generateNumbers, //put here your source of chats from database
builder: (
  BuildContext context,
  AsyncSnapshot<int> snapshot,
) {
  if (snapshot.connectionState == ConnectionState.waiting) {
    return CircularProgressIndicator();
  } else if (snapshot.connectionState == ConnectionState.active
      || snapshot.connectionState == ConnectionState.done) {
    if (snapshot.hasError) {
      return const Text('Error');
    } else if (snapshot.hasData) {
      return Text(
        snapshot.data.toString(),
        style: const TextStyle(color: Colors.teal, fontSize: 36)
      );
    } else {
      return const Text('Empty data');
    }
  } else {
    return Text('State: ${snapshot.connectionState}');
  }
},

希望对您有所帮助

谢谢

0赞 dev 11/11/2023 #2

您正在使用 .you 可以将 ChatMessageCubit 的同一实例传递给所有 BlocBuilder。您正在 initState 中创建一个 chatMessageCubit 实例,只需将相同的实例传递给 BlocBuilders。示例代码如下。ChatMessageCubit

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,
            ),
          ),

评论

0赞 yogeshdinodia 11/11/2023
谢谢先生的帮助,它真的帮助了我,现在我的代码运行良好。