You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何仅触发指定BlocBuilder?解决Flutter聊天消息UI重复重建问题

聊天APP中Bloc触发所有消息UI重建的解决方案

问题核心

开发聊天APP时,使用Bloc/Cubit处理消息的「发送中/已送达/已读」状态,但发送新消息时,所有已发送消息的UI都会被重建,错误显示为「发送中」状态——本质是当前Cubit的全局状态无法关联单个消息,导致所有监听组件同步更新。

现有代码的问题

  1. 全局状态无消息关联:MessageSending/MessageSent是全局状态,没有绑定具体消息ID,所有MessageItemWidget监听同一个Cubit状态,状态变化时全部重建。
  2. 状态与消息分离:消息本身没有存储状态字段,无法区分单条消息的发送状态。

解决方案思路

  • 将消息状态与单个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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 02:33:14