How do I join data from two Firestore collections in Flutter?

前端 未结 6 861
名媛妹妹
名媛妹妹 2020-11-29 02:27

I have a chat app in Flutter using Firestore, and I have two main collections:

  • chats, which is keyed on auto-ids, and has message,
6条回答
  •  攒了一身酷
    2020-11-29 03:14

    I got another version working which seems slightly better than my answer with the two nested builders.

    Here I isolated on the data loading in a custom method, using a dedicated Message class to hold the information from a message Document and the optional associated user Document.

    class Message {
      final message;
      final timestamp;
      final uid;
      final user;
      const Message(this.message, this.timestamp, this.uid, this.user);
    }
    class ChatList extends StatelessWidget {
      Stream> getData() async* {
        var messagesStream = Firestore.instance.collection("chat").orderBy("timestamp", descending: true).snapshots();
        var messages = List();
        await for (var messagesSnapshot in messagesStream) {
          for (var messageDoc in messagesSnapshot.documents) {
            var message;
            if (messageDoc["uid"] != null) {
              var userSnapshot = await Firestore.instance.collection("users").document(messageDoc["uid"]).get();
              message = Message(messageDoc["message"], messageDoc["timestamp"], messageDoc["uid"], userSnapshot["name"]);
            }
            else {
              message = Message(messageDoc["message"], messageDoc["timestamp"], "", "");
            }
            messages.add(message);
          }
          yield messages;
        }
      }
      @override
      Widget build(BuildContext context) {
        var streamBuilder = StreamBuilder>(
              stream: getData(),
              builder: (BuildContext context, AsyncSnapshot> messagesSnapshot) {
                if (messagesSnapshot.hasError)
                  return new Text('Error: ${messagesSnapshot.error}');
                switch (messagesSnapshot.connectionState) {
                  case ConnectionState.waiting: return new Text("Loading...");
                  default:
                    return new ListView(
                      children: messagesSnapshot.data.map((Message msg) {
                        return new ListTile(
                          title: new Text(msg.message),
                          subtitle: new Text(DateTime.fromMillisecondsSinceEpoch(msg.timestamp).toString()
                                             +"\n"+(msg.user ?? msg.uid)),
                        );
                      }).toList()
                    );
                }
              }
            );
            return streamBuilder;
      }
    }
    

    Compared to the solution with nested builders this code is more readable, mostly because the data handling and the UI builder are better separated. It also only loads the user documents for users that have posted messages. Unfortunately, if the user has posted multiple messages, it will load the document for each message. I could add a cache, but think this code is already a bit long for what it accomplishes.

提交回复
热议问题