【问题标题】:Flutter - Chat Screen built with a StreamBuilder showing messages multiple timesFlutter - 使用 StreamBuilder 构建的聊天屏幕,多次显示消息
【发布时间】:2021-09-28 00:31:55
【问题描述】:

我在这个聊天屏幕上苦苦挣扎。该应用程序旨在提出问题(不是以下代码的一部分),用户可以选择答案或键入答案。当用户键入第一个答案时,一切都按计划进行,并显示第一条消息。然而,应用程序会继续显示第二个答案两次,第三个答案三次,依此类推。

我已经面临这个问题几天了,我无法弄清楚为什么应用程序会这样运行。您能否看一下代码并提出解决此问题的方法?

为了给你一些背景信息,这个聊天屏幕是一个更大的应用程序的一部分。当用户打开应用程序时,它应该订阅一个流。然后将每条消息推送到流中,无论是机器人提出的问题还是用户给出的答案。每次流广播某些内容(在我们的例子中是最新的用户输入)时,系统都会监听流并显示一条新消息。

我正在使用从流中构建的消息模型列表来显示消息。为了提出这个问题,我将模型简化到了极致,但实际上它有 23 个字段。创建此消息列表是我设法想到的最佳解决方案,但可能有更好的方法来处理这种情况。如果您知道,请随时告诉我。

这是我正在运行的代码。

import 'package:flutter/material.dart';
import 'dart:async';


StreamController<ChatMessageModel> _chatMessagesStreamController = StreamController<ChatMessageModel>.broadcast();
Stream _chatMessagesStream = _chatMessagesStreamController.stream;

const Color primaryColor = Color(0xff6DA7B9);
const Color secondaryColor = Color(0xffF0F0F0);


void main() {

  runApp(MyApp());
}


class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Chat Screen',
      home: ChatScreen(),
    );
  }
}


class ChatMessageModel {

  final String message;

  const ChatMessageModel({
    this.message,
    }
  );

  factory ChatMessageModel.turnSnapshotIntoListRecord(Map data) {

    return ChatMessageModel(
      message: data['message'],
    );
  }

  @override
  List<Object> get props => [
    message,
  ];
}



class ChatScreen extends StatefulWidget {

  static const String id = 'chat_screen9';

  @override
  _ChatScreenState createState() => _ChatScreenState();
}


class _ChatScreenState extends State<ChatScreen> {

  final _messageTextController = TextEditingController();

  String _userInput;

  @override
  Widget build(BuildContext context) {

    return Scaffold(

      backgroundColor: secondaryColor,

      appBar: AppBar(

        title: Row(
          children: [

            Container(
              padding: EdgeInsets.all(8.0),
              child: Text('Chat Screen',
                style: TextStyle(color: Colors.white,),
              ),
            )
          ],
        ),

        backgroundColor: primaryColor,
      ),

      body: SafeArea(

        child: Column(

          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: <Widget>[

            MessagesStream(),

            Container(
              decoration: BoxDecoration(
                border: Border(
                  top: BorderSide(
                      color: primaryColor,
                      width: 1.0,
                  ),
                ),
              ),

              child: Row(
                crossAxisAlignment: CrossAxisAlignment.center,

                children: <Widget>[

                  Expanded(
                    child: TextField(
                      controller: _messageTextController,
                      onChanged: (value) {

                        _userInput = value;
                      },
                      decoration: InputDecoration(
                        contentPadding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 20.0),
                        hintText: 'Type your answer here',
                        // border: InputBorder.none,
                      ),
                    ),
                  ),

                  TextButton(
                    onPressed: () {

                      _messageTextController.clear();

                      debugPrint('Adding a ChatMessageModel with the message $_userInput to the Stream');

                      ChatMessageModel chatMessageModelRecord = ChatMessageModel(message: _userInput);

                      _chatMessagesStreamController.add(chatMessageModelRecord,);
                    },

                    child: Text(
                      'OK',
                      style: TextStyle(
                        color: primaryColor,
                        fontWeight: FontWeight.bold,
                        fontSize: 18.0,
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}


class MessagesStream extends StatelessWidget {

  List<ChatMessageModel> _allMessagesContainedInTheStream = [];

  @override
  Widget build(BuildContext context) {

    return StreamBuilder<ChatMessageModel>(
      stream: _chatMessagesStream,
      builder: (context, snapshot) {

        _chatMessagesStream.listen((streamedMessages) {

          // _allMessagesContainedInTheStream.clear();

          debugPrint('Value from controller: $streamedMessages');

          _allMessagesContainedInTheStream.add(streamedMessages);
        }
        );

        return Expanded(

          child: ListView.builder(
            // reverse: true,
            padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 20.0),
            itemCount: _allMessagesContainedInTheStream.length,
            itemBuilder: (BuildContext context, int index) {

              if (snapshot.hasData) {

                return UserChatBubble(chatMessageModelRecord: _allMessagesContainedInTheStream[index]);
              }
            },
          ),
        );
      },
    );
  }
}


class UserChatBubble extends StatelessWidget {

  final ChatMessageModel chatMessageModelRecord;

  const UserChatBubble({
    Key key,
    @required this.chatMessageModelRecord,
  }) : super(key: key);


  @override
  Widget build(BuildContext context) {

    return Row(
      mainAxisAlignment: MainAxisAlignment.end,

      children: [

        Padding(
          padding: EdgeInsets.symmetric(vertical: 5, horizontal: 5,),

          child: Container(
            constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 7 / 10,),
            decoration: BoxDecoration(
              borderRadius: BorderRadius.only(
                bottomLeft: Radius.circular(15.0),
                bottomRight: Radius.circular(15.0),
                topLeft: Radius.circular(15.0),
              ),
              color: primaryColor,
            ),
            padding: EdgeInsets.symmetric(vertical: 8, horizontal: 20,),

            child: Text(chatMessageModelRecord.message,
              style: TextStyle(
                fontSize: 17,
                // fontWeight: FontWeight.w500,
                color: Colors.white,
              ),
            ),
          ),
        ),
      ],
    );
  }
}

【问题讨论】:

    标签: flutter dart listview chat stream-builder


    【解决方案1】:

    首先,感谢您提供有趣的问题和功能示例。我必须做一些小改动才能将其转换为“null-safety”,但我的代码也应该可以在您的计算机上运行。

    您在初始化 _chatMessagesStream 侦听器时遇到的唯一问题。您应该只调用一次,最好在initState 中调用一次。

    所以这里是你的解决方法:

    class MessagesStream extends StatefulWidget {
      @override
      _MessagesStreamState createState() => _MessagesStreamState();
    }
    
    class _MessagesStreamState extends State<MessagesStream> {
      final List<ChatMessageModel> _allMessagesContainedInTheStream = [];
    
      @override
      void initState() {
        _chatMessagesStream.listen((streamedMessages) {
          // _allMessagesContainedInTheStream.clear();
    
          debugPrint('Value from controller: $streamedMessages');
    
          _allMessagesContainedInTheStream.add(streamedMessages);
        });
        super.initState();
      }
    
      @override
      Widget build(BuildContext context) {
        return StreamBuilder<ChatMessageModel>(
          stream: _chatMessagesStream,
          builder: (context, snapshot) {
            return Expanded(
              child: ListView.builder(
                // reverse: true,
                padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 20.0),
                itemCount: _allMessagesContainedInTheStream.length,
                itemBuilder: (BuildContext context, int index) {
                  if (snapshot.hasData) {
                    return UserChatBubble(
                      chatMessageModelRecord:
                          _allMessagesContainedInTheStream[index],
                    );
                  } else {
                    print(snapshot.connectionState);
                    return Container();
                  }
                },
              ),
            );
          },
        );
      }
    }
    

    还提供完整的 null 安全代码以防万一!

    import 'package:flutter/material.dart';
    import 'dart:async';
    
    final StreamController<ChatMessageModel> _chatMessagesStreamController =
        StreamController<ChatMessageModel>.broadcast();
    final Stream<ChatMessageModel> _chatMessagesStream =
        _chatMessagesStreamController.stream;
    
    const Color primaryColor = Color(0xff6DA7B9);
    const Color secondaryColor = Color(0xffF0F0F0);
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Chat Screen',
          home: ChatScreen(),
        );
      }
    }
    
    class ChatMessageModel {
      final String? message;
    
      const ChatMessageModel({
        this.message,
      });
    
      factory ChatMessageModel.turnSnapshotIntoListRecord(Map data) {
        return ChatMessageModel(
          message: data['message'],
        );
      }
    
      List<Object> get props => [
            message!,
          ];
    }
    
    class ChatScreen extends StatefulWidget {
      static const String id = 'chat_screen9';
    
      @override
      _ChatScreenState createState() => _ChatScreenState();
    }
    
    class _ChatScreenState extends State<ChatScreen> {
      final _messageTextController = TextEditingController();
    
      String? _userInput;
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          backgroundColor: secondaryColor,
          appBar: AppBar(
            title: Row(
              children: [
                Container(
                  padding: EdgeInsets.all(8.0),
                  child: Text(
                    'Chat Screen',
                    style: TextStyle(
                      color: Colors.white,
                    ),
                  ),
                )
              ],
            ),
            backgroundColor: primaryColor,
          ),
          body: SafeArea(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: <Widget>[
                MessagesStream(),
                Container(
                  decoration: BoxDecoration(
                    border: Border(
                      top: BorderSide(
                        color: primaryColor,
                        width: 1.0,
                      ),
                    ),
                  ),
                  child: Row(
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: <Widget>[
                      Expanded(
                        child: TextField(
                          controller: _messageTextController,
                          onChanged: (value) {
                            _userInput = value;
                          },
                          decoration: InputDecoration(
                            contentPadding: EdgeInsets.symmetric(
                                vertical: 10.0, horizontal: 20.0),
                            hintText: 'Type your answer here',
                            // border: InputBorder.none,
                          ),
                        ),
                      ),
                      TextButton(
                        onPressed: () {
                          _messageTextController.clear();
    
                          debugPrint(
                              'Adding a ChatMessageModel with the message $_userInput to the Stream');
    
                          ChatMessageModel chatMessageModelRecord =
                              ChatMessageModel(message: _userInput);
    
                          _chatMessagesStreamController.add(
                            chatMessageModelRecord,
                          );
                        },
                        child: Text(
                          'OK',
                          style: TextStyle(
                            color: primaryColor,
                            fontWeight: FontWeight.bold,
                            fontSize: 18.0,
                          ),
                        ),
                      ),
                    ],
                  ),
                ),
              ],
            ),
          ),
        );
      }
    }
    
    class MessagesStream extends StatefulWidget {
      @override
      _MessagesStreamState createState() => _MessagesStreamState();
    }
    
    class _MessagesStreamState extends State<MessagesStream> {
      final List<ChatMessageModel> _allMessagesContainedInTheStream = [];
    
      @override
      void initState() {
        _chatMessagesStream.listen((streamedMessages) {
          // _allMessagesContainedInTheStream.clear();
    
          debugPrint('Value from controller: $streamedMessages');
    
          _allMessagesContainedInTheStream.add(streamedMessages);
        });
        super.initState();
      }
    
      @override
      Widget build(BuildContext context) {
        return StreamBuilder<ChatMessageModel>(
          stream: _chatMessagesStream,
          builder: (context, snapshot) {
            return Expanded(
              child: ListView.builder(
                // reverse: true,
                padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 20.0),
                itemCount: _allMessagesContainedInTheStream.length,
                itemBuilder: (BuildContext context, int index) {
                  if (snapshot.hasData) {
                    return UserChatBubble(
                      chatMessageModelRecord:
                          _allMessagesContainedInTheStream[index],
                    );
                  } else {
                    print(snapshot.connectionState);
                    return Container();
                  }
                },
              ),
            );
          },
        );
      }
    }
    
    class UserChatBubble extends StatelessWidget {
      final ChatMessageModel chatMessageModelRecord;
    
      const UserChatBubble({
        Key? key,
        required this.chatMessageModelRecord,
      }) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Row(
          mainAxisAlignment: MainAxisAlignment.end,
          children: [
            Padding(
              padding: EdgeInsets.symmetric(
                vertical: 5,
                horizontal: 5,
              ),
              child: Container(
                constraints: BoxConstraints(
                  maxWidth: MediaQuery.of(context).size.width * 7 / 10,
                ),
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.only(
                    bottomLeft: Radius.circular(15.0),
                    bottomRight: Radius.circular(15.0),
                    topLeft: Radius.circular(15.0),
                  ),
                  color: primaryColor,
                ),
                padding: EdgeInsets.symmetric(
                  vertical: 8,
                  horizontal: 20,
                ),
                child: Text(
                  "${chatMessageModelRecord.message}",
                  style: TextStyle(
                    fontSize: 17,
                    // fontWeight: FontWeight.w500,
                    color: Colors.white,
                  ),
                ),
              ),
            ),
          ],
        );
      }
    }
    

    【讨论】:

    • 感谢您的快速回答和解释。主要的收获是在错误的地方订阅了流,这样做实际上是在每次发生新事件时重新订阅。我完全错过了这一点,但事后看来这是有道理的。谢谢,这很有帮助。也感谢您指出 null 安全问题以及如何解决它。
    猜你喜欢
    • 2011-11-12
    • 1970-01-01
    • 1970-01-01
    • 2021-06-16
    • 1970-01-01
    • 2017-06-01
    • 2022-11-18
    • 1970-01-01
    • 2023-02-14
    相关资源
    最近更新 更多