Flutter asyncMap 直到 setState 才会运行

Posted

技术标签:

【中文标题】Flutter asyncMap 直到 setState 才会运行【英文标题】:Flutter asyncMap not run until setState 【发布时间】:2021-04-16 03:21:46 【问题描述】:

我正在制作一个聊天应用程序,它在同一个列表中显示群聊和私人聊天。

我使用 Firestore 作为数据库,并在其中存储用户、组和联系人的数据。我有一个消息屏幕,显示用户使用StreamBuilder 的聊天列表。

我想根据组的数据以不同的方式显示数据。群聊有他们的群组图片、与联系人中的用户进行私人聊天、他们的头像显示,以及带有通用图标显示的私人聊天与用户不在联系人中。

我首先在 DatabaseService 类中遍历流,然后将其放入变量中并将其设置为StreamBuilder 的流。这很好用,但我还想要一个列表来检查用户是否已经与另一个用户进行了私人聊天,而无需从 Firestore 获取数据。

API.dart

//this is where I put my code to connect and read/write data from Firestore

final FirebaseFirestore _db = FirebaseFirestore.instance;

Api();

....

Stream<QuerySnapshot> streamCollectionByArrayAny(
      String path, String field, dynamic condition) 
    return _db
        .collection(path)
        .where(field, arrayContainsAny: condition)
        .snapshots();
  

DatabaseService.dart

...
List<GroupModel> groups; //List of Groups
Stream<List<GroupModel>> groupStream; //Stream of List Group
...
Stream<QuerySnapshot> fetchGroupsByMemberArrayAsStream(
      String field, dynamic condition) 
    return _api.streamCollectionByArrayAny('groups', field, condition);
  
//function to get Contact Detail using List of Group User
Future<ContactModel> getContactDetail(List<dynamic> members) async 

    //remove current user id from the list
    members.removeWhere((element) => element.userId == user.userId);

    //getContactbyId return a ContactModel object from Firestore
    ContactModel contactModel =
        await getContactById(user.userId, members.first.userId);

    if (contactModel != null && contactModel.userId.isNotEmpty) 
      return contactModel;
     else 
      return new ContactModel(
          userId: members.first.userId, nickname: "", photoUrl: "");
    
  

  Future<GroupModel> generateGroupMessage(GroupModel group) async 

    //check if Group Chat or Private chat
    if (group.type == 1) 
      ContactModel contactModel = await getContactDetail(group.membersList);
      group.groupName = contactModel.nickname.isNotEmpty
          ? contactModel.nickname
          : contactModel.userId;
      group.groupPhoto = contactModel.photoUrl;
    
    print("Add");
    
    //add the group data into List<GroupModel> groups
    groups.add(group);
    return group;
  

  void refreshMessageList() 
    groups = [];
    print("refresh");

    //get Group Data as Stream from FireStore base on the user data in the Member Array of Group then map it to Stream while also change data base on Group type in generateGroupMessage
    groupStream = fetchGroupsByMemberArrayAsStream('membersList', [
      "isActive": true, "role": 1, "userId": user.userId,
      "isActive": true, "role": 2, "userId": user.userId
    ]).asyncMap((docs) => Future.wait([
          for (GroupModel group in docs.docs
              .map((doc) => GroupModel.fromMap(doc.data()))
              .toList())
            generateGroupMessage(group)
        ]));
  

Message.dart

@override
  void initState() 
    super.initState();
    ...
    databaseService.refreshMessageList();
    setState(() );
  

@override
  Widget build(BuildContext context) 
    return Scaffold(
      body: Container(
        width: MediaQuery.of(context).size.width,
        padding: EdgeInsets.symmetric(horizontal: 16),
        margin: EdgeInsets.only(top: 24),
        child: Column(
          children: [
          ...
          Flexible(
              child: StreamBuilder(
                stream: databaseService.groupStream,
                builder: (context, AsyncSnapshot<List<GroupModel>> snapshot) 
                  if (!snapshot.hasData) 
                    print("No data");
                    return Center(
                      child: CircularProgressIndicator(
                        valueColor: AlwaysStoppedAnimation<Color>(Colors.grey),
                      ),
                    );
                   else 
                    print("Has data");
                    groups = List.from(snapshot.data);
                    groups.removeWhere(
                        (element) => element.recentMessageContent.isEmpty);
                    groups.sort((group1, group2) 
                      if (DateTime.parse(group1.recentMessageTime)
                          .isAfter(DateTime.parse(group2.recentMessageTime))) 
                        return -1;
                       else 
                        return 1;
                      
                    );
                    return ListView.builder(
                        padding: EdgeInsets.all(10.0),
                        itemBuilder: (context, index) =>
                            buildItem(context, groups[index]),
                        itemCount: groups.length,
                      ),
                    ),
                  ),
                
  ],)));


Widget buildItem(BuildContext context, GroupModel group) 
    if (group.recentMessageContent == '') 
      return Container();
     else 
      return Column(
        children: [
          Container(
            child: InkWell(
                child: Row(
                  children: <Widget>[
                    Material(
                      child: group.groupPhoto.isNotEmpty
                          ? CachedNetworkImage(
                              placeholder: (context, url) => Container(
                                child: CircularProgressIndicator(
                                  strokeWidth: 1.0,
                                  valueColor: AlwaysStoppedAnimation<Color>(
                                      Colors.grey),
                                ),
                                width: 60.0,
                                height: 60.0,
                                padding: EdgeInsets.all(10.0),
                              ),
                              imageUrl: group.groupPhoto,
                              width: 60.0,
                              height: 60.0,
                              fit: BoxFit.cover,
                            )
                          : Icon(
                              group.type == 1
                                  ? Icons.account_circle
                                  : Icons.group,
                              size: 60.0,
                              color: Colors.grey,
                            ),
                      borderRadius: BorderRadius.all(Radius.circular(30.0)),
                      clipBehavior: Clip.hardEdge,
                    ),
                    SizedBox(
                      width: 150,
                      child: Container(
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: <Widget>[
                            Text(
                              group.groupName,
                              style: TextStyle(
                                  color: colorBlack,
                                  fontSize: 12,
                                  fontWeight: FontWeight.bold),
                              overflow: TextOverflow.ellipsis,
                            ),
                            Text(
                              group.recentMessageContent,
                              style: TextStyle(
                                  color: Colors.grey,
                                  fontSize: 10,
                                  height: 1.6),
                              overflow: TextOverflow.ellipsis,
                            ),
                          ],
                        ),
                        margin: EdgeInsets.only(left: 12.0),
                      ),
                    ),
                    Spacer(),
                    Text(
                      formatDateTime(group.recentMessageTime),
                      style: TextStyle(color: Colors.grey, fontSize: 10),
                    ),
                  ],
                ),
                onTap: () 
                  switch (group.type) 
                    case 1:
                      Navigator.of(context, rootNavigator: true)
                          .push(MaterialPageRoute(
                              settings:
                                  RouteSettings(name: "/message/chatPage"),
                              builder: (context) => ChatPage(group: group)))
                          .then((value) => setState);
                      break;
                    case 2:
                      Navigator.of(context, rootNavigator: true)
                          .push(MaterialPageRoute(
                              settings:
                                  RouteSettings(name: "/message/chatGroup"),
                              builder: (context) =>
                                  ChatGroupPage(group: group)))
                          .then((value) => setState(() ));
                      break;
                  
                ),
          ),
          Divider(
            color: Colors.grey,
          ),
        ],
      );
    
  

ChatPageChatGroupPage 分别导航到私人聊天和群聊,用户可以在其中将聊天伙伴或群组成员添加到联系人中。

添加完成后,我调用databaseService.refreshMessageList 来刷新列表组的流,因此当我导航回消息屏幕时,它将相应地刷新并显示。但是,List&lt;GroupModel&gt; 组变为空白,并且在我导航回消息屏幕之前不会添加数据。

我调试了应用程序,发现列表变为空白,因为它执行 groups = [] 但没有运行 .asyncMap,直到我热重载或导航消息屏幕并将 setState 放入 .then 以刷新数据.

添加到联系人时,我需要列表组来检查 2 个用户是否已经进行了私人聊天以创建新的。我已经尝试将setState 放在databaseService.refreshMessageList 之后,但还是不行。

谁能帮助我并提供解决方案?我知道这不是一个好问题,但我已经被这个问题困扰了将近一个星期,迫切需要一个答案。提前谢谢你。

编辑

这是我的数据结构:

用户

/users (collection)
    /userId
        /user (document)
        - userId
        - nickname
        - photoUrl
        - token
        - /contacts (subcollection)
              /contactId
                  /contact (document)
                  - userId
                  - nickname
                  - photoUrl

组:

/groups (collection)
    /groupId
        /group (document)
        - groupId
        - groupName
        - type
        - membersList (List<Map<String, dynamic>>)
              - member: userId, isActive, role
        - recentMessageContent
        - recentMessageTime
        - recentMessageType

消息:

/messages (collection)
    /groupId
        /groupMessage (document)
             /messages (subcollection)
                  /messageId
                       /message (document)
                       - messageContent
                       - messageTime
                       - messageType

【问题讨论】:

您能否也将您的数据结构添加到问题中?这将有助于了解您的配置。 当然,我已经在问题中添加了它。如果需要,请查看。 【参考方案1】:

您可以使用array membership,例如,array-contains 方法可以在不执行任何操作的情况下查询数组中的元素。有一个有趣的article 提供了一些您可能感兴趣的示例。

另一种选择是迭代两个数组,直到匹配您需要的值。但是,如果没有正确实现,迭代可能会导致performance issues。

【讨论】:

感谢您的回答。但是,我不确定这将如何解决我的问题。我的问题似乎是 .asyncMap 在 setState 重新加载或手动热重新加载之前不会运行,但在我退出 ChatPageMessages 屏幕之前它不会运行。 fetchGroupsByMemberArrayAsStream 已经使用 array-contains 将数据作为 QuerySnapshots 流回,因为我的 membersList 数组中包含地图,我提供了完整的地图数据,例如"isActive": true, "role": 2, "userId": user.userId 用于搜索,所以使用它不是问题 我在您提供的fetchGroupsByMemberArrayAsStream 代码sn-p 中看不到array-contains。你有没有设法让它工作?看看问题出在哪里会很有趣。 你可以在Debugging Flutter Apps helpful找到这个链接 我没有,但我能够通过将.asyncMap 中的函数拆分为另一个函数并将addGroup 放在那里以填充group 数组,从而将数据添加到group 数组中.当我运行调试器并在 ChatPage 中执行 refreshMessageList 函数时,我将断点放在所有函数行上,并看到它执行 fetchGroupsByMemberArrayAsStream 但之后不执行 .asyncMap 并且只有当我导航回 @ 987654340@ 我将 setState 放入 .then 的位置,它是否继续运行 .asyncMap。 我将更新代码以包含 fetchGroupsByMemberArrayAsStream 确切代码

以上是关于Flutter asyncMap 直到 setState 才会运行的主要内容,如果未能解决你的问题,请参考以下文章

Flutter:我应该如何访问此删除请求中的响应正文?

Flutter - 在 GridView 中禁用滚动,直到孩子填满高度

Flutter Navigator 移除直到

在构建异常期间调用的 setState() 或 markNeedsBuild() 阻止我执行回调

如何在flutter中实现日历功能,以便在DateTimePickerFormField中显示当前日期之后的日期,直到2天之后

带有 Firebase 身份验证和收入 cat 的 IAP 的 Flutter 应用程序未连接