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

Flutter使用flutter_chat_ui点击消息报错:RenderSliverPadding子类型不匹配

问题:flutter_chat_ui点击消息触发RenderSliverPadding类型不匹配错误

使用flutter_chat_ui开发聊天机器人,初始消息能正常获取展示,但点击任意初始消息时触发报错:

Error: A RenderSliverPadding expected a child of type RenderSliver but received a child of type RenderErrorBox

点击操作本应向消息列表新增消息并展示,却出现上述错误。RenderObjects需要特定类型子组件配合布局绘制,比如RenderSliver不能作为RenderBox的子组件,两者布局协议不兼容。

ChatDemo类代码

class _ChatScreenDemo2 extends State<ChatScreenDemo2> {
  final GlobalKey<SliverAnimatedListState> _sliverAnimatedListKey =
      GlobalKey<SliverAnimatedListState>();
  ChatScreenViewModel chatScreenVM = ChatScreenViewModel();
  ChatController? _chatController;

  List<types.TextMessage> messages = [];
  final String User = 'user_1'; // Example user ID
  final String Admin = 'user_2';

  @override
  void initState() {
    super.initState();
    chatScreenVM.fetchInitialMessage();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Chat'),
      ),
      // body: Column(
      //   mainAxisSize: MainAxisSize.min,
      //   children: [
      //     Flexible(
      //       child: Observer(builder: (_) {
      //         return Chat(
      //           messages: chatScreenVM.initialMessageList.reversed.toList(),
      //           onSendPressed: _handleSendPressed,
      //           user: types.User(id: User),
      //           onMessageTap: _handleMessageTap,
      //         );
      //       }),
      //     ),
      //   ],
      // ),

      body: Container(
        height: MediaQuery.of(context).size.height, // Set a fixed height

        child: Observer(builder: (_) {
          return Chat(
            messages: chatScreenVM.initialMessageList.reversed.toList(),
            onSendPressed: _handleSendPressed,
            user: types.User(id: User),
            onMessageTap: _handleMessageTap,
          );
        }),
      ),
    );
  }

  void _handleSendPressed(types.PartialText message) {
    final newMessage = types.TextMessage(
      author: types.User(id: User),
      createdAt: DateTime.now().millisecondsSinceEpoch,
      id: DateTime.now().millisecondsSinceEpoch.toString(),
      text: message.text,
    );
    setState(() {
      messages.add(newMessage);
    });
  }

  void _handleMessageTap(BuildContext context, types.Message message) {
    if (message is types.TextMessage) {
      final newMessage = types.TextMessage(
        author: types.User(id: "user_1"),
        id: message.id,
        createdAt: DateTime.now().millisecondsSinceEpoch - 10000,
        text: message.text,
      );
      setState(() {
        chatScreenVM.updateMessageList(newMessage);
        print("${message.id}");
        print("Cliked");
        chatScreenVM.fetchResponse(message.id);
      });
    }
  }
}

Chat屏幕视图模型代码

import 'dart:convert';

import 'package:echarak/Modal/chat_screen_inital_message_response_model.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:mobx/mobx.dart';

import '../Modal/chat_screen_initial_message_model.dart';
import '../constant/base_url.dart';
import 'package:http/http.dart' as http;
import 'package:flutter_chat_types/flutter_chat_types.dart' as types;


part 'chat_screen_view_model.g.dart';


class ChatScreenViewModel = _ChatScreenViewModel with _$ChatScreenViewModel;

abstract class _ChatScreenViewModel with Store{
  final FlutterSecureStorage secureStorage = FlutterSecureStorage(); // Initialize secure storage

  @observable
  ObservableList<types.TextMessage> initialMessageList = ObservableList();

  @action
  Future<void> updateMessageList(types.TextMessage value) async{
    try{
      initialMessageList.add(value);
    }catch(e){
      print(e.toString());
    }
  }

  @action
  Future<void> fetchInitialMessage()async{
    try {
      final token = await secureStorage.read(key: 'token');
      final response = await http.post(
        Uri.parse('${AppUrls.BASE_URL}/GetTer'),
        headers: {
          "Content-Type": "application/json",
          'Authorization': 'Bearer $token', //
        },
      );

      if (response.statusCode == 200) {
        final List<dynamic> responseBody = json.decode(response.body);
        // Map response to initialMessageList
        initialMessageList = ObservableList<types.TextMessage>.of(
          responseBody.map((item) {
            final chatScreenInitial = ChatScreenInitial.fromJson(item);
            return types.TextMessage(
              author: types.User(id: "user_2"),
              id: chatScreenInitial.id.toString(),
              createdAt:  DateTime.now().millisecondsSinceEpoch - 10000,
              text: chatScreenInitial.type_of_query ?? '', // Fallback if null
            );
          }).toList(),
        );
        print("Initial Message list:$responseBody");
      }
      else {
        print("Error: ${response.statusCode}");
      }
    }catch(e){
      print(e.toString());
    }
  }

  @action
  Future<void> fetchResponse(String id) async{
    try {
      final token = await secureStorage.read(key: 'token');
      final response = await http.post(
        Uri.parse('${AppUrls.BASE_URL}/GetsubTer/$id'),
        headers: {
          "Content-Type": "application/json",
          'Authorization': 'Bearer $token', //
        },
      );

      if (response.statusCode == 200) {
        final List<dynamic> responseBody = json.decode(response.body);
        final responseList = ObservableList<types.TextMessage>.of(
          responseBody.map((item) {
            final chatScreenInitialResonse = ChatScreenInitialResponse.fromJson(item);
            return types.TextMessage(
              author: types.User(id: "user_2"),
              id: chatScreenInitialResonse.id.toString(),
              createdAt:  DateTime.now().millisecondsSinceEpoch - 10000,
              text: chatScreenInitialResonse.subTypeOfQuery ?? '', // Fallback if null
            );
          }).toList(),
        );
        initialMessageList.addAll(responseList);
        print("Initial Message list:$responseBody");
      }
      else {
        print("Error: ${response.statusCode}");
      }
    }catch(e){
      print(e.toString());
    }
  }
}

解决方案

1. 修复布局容器冲突

Chat组件内部依赖Sliver布局体系,固定高度的Container会破坏其布局逻辑,改用Column+Flexible结构让组件自适应:

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: const Text('Chat')),
    body: Column(
      children: [
        Flexible(
          child: Observer(builder: (_) {
            return Chat(
              messages: chatScreenVM.initialMessageList.reversed.toList(),
              onSendPressed: _handleSendPressed,
              user: types.User(id: User),
              onMessageTap: _handleMessageTap,
            );
          }),
        ),
      ],
    ),
  );
}

2. 解决消息ID重复问题

点击添加的新消息使用了原消息的ID,会导致组件内部渲染异常,需生成唯一ID:

void _handleMessageTap(BuildContext context, types.Message message) {
  if (message is types.TextMessage) {
    final newMessage = types.TextMessage(
      author: types.User(id: "user_1"),
      id: DateTime.now().millisecondsSinceEpoch.toString(), // 生成唯一ID
      createdAt: DateTime.now().millisecondsSinceEpoch,
      text: message.text,
    );
    chatScreenVM.updateMessageList(newMessage);
    print("${message.id}");
    print("Clicked");
    chatScreenVM.fetchResponse(message.id);
  }
}

3. 移除不必要的setState

因为使用MobX的ObservableList,列表更新后Observer会自动触发UI重建,无需额外调用setState,包括_handleSendPressed方法也可以去掉:

void _handleSendPressed(types.PartialText message) {
  final newMessage = types.TextMessage(
    author: types.User(id: User),
    createdAt: DateTime.now().millisecondsSinceEpoch,
    id: DateTime.now().millisecondsSinceEpoch.toString(),
    text: message.text,
  );
  chatScreenVM.updateMessageList(newMessage); // 直接更新ObservableList
}

4. 规范时间戳逻辑

避免使用固定偏移的时间戳,确保新消息时间戳晚于旧消息,保证消息排序正确,推荐使用DateTime.now().millisecondsSinceEpoch作为消息创建时间。

内容的提问来源于stack exchange,提问作者Jaivik

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 18:47:32