如何仅触发指定BlocBuilder?解决Flutter聊天消息UI重复重建问题
聊天APP中Bloc触发所有消息UI重建的解决方案
问题核心
开发聊天APP时,使用Bloc/Cubit处理消息的「发送中/已送达/已读」状态,但发送新消息时,所有已发送消息的UI都会被重建,错误显示为「发送中」状态——本质是当前Cubit的全局状态无法关联单个消息,导致所有监听组件同步更新。
现有代码的问题
- 全局状态无消息关联:
MessageSending/MessageSent是全局状态,没有绑定具体消息ID,所有MessageItemWidget监听同一个Cubit状态,状态变化时全部重建。 - 状态与消息分离:消息本身没有存储状态字段,无法区分单条消息的发送状态。
解决方案思路
- 将消息状态与单个
Message模型绑定,添加状态字段跟踪每条消息的发送状态。 - 用
MessagesLoaded状态维护包含状态的消息列表,发送消息时仅更新对应消息的状态,而非触发全局状态。 - 修改UI组件,根据当前消息的状态渲染UI,而非依赖全局的发送状态。
代码修改示例
1. 更新Message模型,添加状态字段
enum MessageStatus { sending, sent, read } class Message extends Equatable { final String id; final String content; final MessageStatus status; // 其他字段(如发送者、时间等) const Message({ required this.id, required this.content, required this.status, // 其他参数 }); // 复制方法,用于更新状态时不修改原对象 Message copyWith({MessageStatus? status}) { return Message( id: id, content: content, status: status ?? this.status, // 复制其他字段 ); } @override List<Object> get props => [id, content, status]; }
2. 修改MessagesCubit逻辑,维护单条消息状态
去掉全局的发送状态,改为在消息列表中更新对应消息的状态:
class MessagesCubit extends Cubit<MessagesState> { ChatRepository chatRepository; MessagesCubit(this.chatRepository) : super(MessagesInitial()); Future<void> getMessages( BuildContext context, { required UserChat client, required UserChat supplier, }) async { emit(MessagesLoading()); final failureOrStreamOfMessages = await chatRepository.getMessages(client: client, supplier: supplier); failureOrStreamOfMessages.fold( (failure) => emit(MessagesError(mapFailureToMessage(failure, context))), (streamOfMessages) => streamOfMessages.listen( (messages) => emit(MessagesLoaded(messages)), ), ); } Future<void> sendMessage(BuildContext context, Message message) async { // 先获取当前消息列表,添加状态为sending的消息 if (state is MessagesLoaded) { final updatedMessages = [ ...(state as MessagesLoaded).messages, message.copyWith(status: MessageStatus.sending), ]; emit(MessagesLoaded(updatedMessages)); } // 调用仓库发送消息 final failureOrUnit = await chatRepository.sendMessage(message); failureOrUnit.fold( (failure) { // 发送失败:更新消息状态并触发错误提示 if (state is MessagesLoaded) { final updatedMessages = (state as MessagesLoaded).messages.map((m) { if (m.id == message.id) { return m.copyWith(status: MessageStatus.sent); // 可自定义错误状态 } return m; }).toList(); emit(MessagesLoaded(updatedMessages)); } emit(MessagesError(mapFailureToMessage(failure, context))); }, (_) { // 发送成功:更新对应消息的状态为sent if (state is MessagesLoaded) { final updatedMessages = (state as MessagesLoaded).messages.map((m) { if (m.id == message.id) { return m.copyWith(status: MessageStatus.sent); } return m; }).toList(); emit(MessagesLoaded(updatedMessages)); } }, ); } }
3. 优化MessageItemWidget,根据单条消息状态渲染
不再监听全局发送状态,而是从MessagesLoaded中获取当前消息的最新状态:
class MessageItemWidget extends StatelessWidget { final Message message; final Alignment alignment; final BorderRadius borderRadius; final TextStyle chatStyle; final Color color; const MessageItemWidget({ Key? key, required this.message, required this.alignment, required this.borderRadius, required this.chatStyle, required this.color, }) : super(key: key); @override Widget build(BuildContext context) { return BlocBuilder<MessagesCubit, MessagesState>( builder: (context, state) { if (state is! MessagesLoaded) { return const SizedBox(); } // 获取当前消息的最新状态 final currentMessage = state.messages.firstWhere( (m) => m.id == message.id, orElse: () => message, ); return Align( alignment: alignment, child: UnconstrainedBox( child: Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Container( margin: const EdgeInsets.all(8), padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), constraints: BoxConstraints( maxWidth: MediaQuery.of(context).size.width * 0.55, ), decoration: BoxDecoration( borderRadius: borderRadius, color: color, ), child: Text( currentMessage.content, style: chatStyle, ), ), // 根据消息状态显示对应图标 if (currentMessage.status == MessageStatus.sending) const Icon(Icons.check, color: Colors.grey, size: 15) else if (currentMessage.status == MessageStatus.sent) const Row( children: [ Icon(Icons.check, color: Colors.blue, size: 15), Icon(Icons.check, color: Colors.blue, size: 15), ], ) else if (currentMessage.status == MessageStatus.read) const Icon(Icons.check_circle, color: Colors.blue, size: 15) ], ), ), ); }, ); } }
4. 简化状态定义,移除全局发送状态
删除WritingMessagesState相关类,统一用MessagesLoaded维护消息列表:
abstract class MessagesState extends Equatable { const MessagesState(); @override List<Object> get props => []; } class MessagesInitial extends MessagesState {} abstract class ReadingMessagesState extends MessagesState { const ReadingMessagesState(); } class MessagesLoading extends ReadingMessagesState {} class MessagesLoaded extends ReadingMessagesState { final List<Message> messages; const MessagesLoaded(this.messages); @override List<Object> get props => [messages]; } class MessagesError extends ReadingMessagesState { final String message; const MessagesError(this.message); @override List<Object> get props => [message]; }
内容的提问来源于stack exchange,提问作者Mikelenjilo
相关产品推荐
相关产品推荐

