Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
815 views
in Technique[技术] by (71.8m points)

firebase - Flutter asyncMap not run until setState

I am making a chat app that displays both a Group Chat and Private Chat in the same List.

I use Firestore as the database and store the data of User, Group and Contact in there. I have a Message Screen that displays a list of Chats that the User has using StreamBuilder.

I want to display data differently depending on the group's data. The group chat has their Group picture, Private Chat with User in Contact, their avatar display, and Private Chat with a generic icon display with User not in Contact.

I iterate through the stream first in a DatabaseService class, then put it in a variable and set it as a stream for StreamBuilder. This works fine, but I also want a list to check if a user already has a private chat with another User without getting the data from 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,
          ),
        ],
      );
    }
  }

The ChatPage and ChatGroupPage navigate to Private Chat and Group Chat respectively, and in there the User can add the chat partner or group member into Contact.

When adding is done I call the databaseService.refreshMessageList to refresh the Stream of List Group, so when I navigate back to the Message Screen, it will refresh and display accordingly. However, the List<GroupModel> groups becomes blank and will not add data until I navigate back to the Message Screen.

I debugged the app and found that the List became blank because it executes groups = [] but did not run the .asyncMap until I hot reload or navigate Message Screen and put the setState in .then to refresh the data.

I need the List groups to check whether the 2 users already have a private chat to create a new one when adding to Contact. I have already tried putting setState after databaseService.refreshMessageList, but it still did not work.

Can anyone please help me and provide a solution? I know this is not a good question to ask, but I have been stuck with this for almost a week now and desperately need an answer. Thank you in advance.

EDIT

Here is my data structure:

Users

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

Groups:

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You can use array membership, for example, the array-contains method can query for elements within an array without performing any manipulation. There is an interesting article that provides some examples you might interest you.

Another alternative could be to iterate both arrays until matching the values you need. However, iteration can lead to performance issues if you do not implement it correctly.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...