Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to efficiently access a firestore reference field's data in flutter?

Using similar code as flutter's firestore example, suppose there is a reference field stored in a snapshot document, called: document['userRef'].

First of all, how do I access the data of userRef? Using document['userRef'].get().data or document['userRef'].get().username I wasn't able to access the data. (NoSuchMethodError: Class 'Future<DocumentSnapshot>' has no instance getter 'data')

I also tried using document['userRef'].get().then(...) but getting the error: type 'Future<dynamic>' is not a subtype of type 'String'

Even if .then would work, wouldn't it then look up the same reference again for each message? Here the database is updated in realtime, but it's unnecessary to make the same lookup for multiple messages in the ListView.

class MessageList extends StatelessWidget {
  MessageList({this.firestore});

  final Firestore firestore;

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<QuerySnapshot>(
      stream: firestore.collection('messages').snapshots(),
      builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
        if (!snapshot.hasData) return const Text('Loading...');
        final int messageCount = snapshot.data.documents.length;
        return ListView.builder(
          itemCount: messageCount,
          itemBuilder: (_, int index) {
            final DocumentSnapshot document = snapshot.data.documents[index];
            // document['userRef'] exists here
            return ListTile(
              title: Text(document['message'] ?? '<No message retrieved>'),
              subtitle: Text('Message ${index + 1} of $messageCount'),
            );
          },
        );
      },
    );
  }
}

Edit: I was able to fetch the nested data using FutureBuilder, though not sure how efficient it is. (Wouldn't this possibly send loads of redundant requests to Firebase?)

Creating a widget for the nested data, where document['userRef'] exists:

        FutureBuilder(
          future: userData(document['userRef']),
          builder: (BuildContext context,
              AsyncSnapshot<dynamic> uData) {
            return Text(uData.data['username']);
          },
        );

And the userData function looks like this:

Future<dynamic> userData(DocumentReference user) async {
  DocumentSnapshot userRef = await user.get();
  return userRef.data;
}
like image 870
Robin Manoli Avatar asked Oct 19 '18 06:10

Robin Manoli


People also ask

How do you increment value in firestore Flutter?

You can use FieldValue. increment with a double (e.g. FieldValue. increment(1.0) ) or an int (e.g. FieldValue. increment(2) ).

Why are my firestore reads so high?

Use of the Firebase console will incur reads. If you leave the console open on a collection or document with busy write activity then the Firebase console will automatically read the changes that update the console's display. Most of the time this is the reason for unexpected high reads. You can go through this answer.


1 Answers

Sticking to the Firebase and Flutter way, it is possible to use a Streambuilder inside a Streambuilder. That is, instead of using a FutureBuilder for the nested data, which makes you wait for each .get request.

(The code is untested, but the principle is tested.)

class MessageList extends StatelessWidget {
  MessageList({this.firestore});

  final Firestore firestore;

  @override
  Widget build(BuildContext context) {
    Map UserSnapshot = Map(); // create a variable for accessing users by id

    return StreamBuilder<QuerySnapshot>(
        stream: firestore.collection('users').snapshots(),
        builder:
            (BuildContext context, AsyncSnapshot<QuerySnapshot> UsersSnapshot) {
          // process usersnapshot from list to map
          UsersSnapshot.data.documents.forEach((userRecord) {
            //print(optionRecord.documentID); // debug
            UserSnapshot[userRecord.documentID] = userRecord;
          });
          // user data can be accessed as soon as there is a reference field or documentID:
          // UserSnapshot[document['userRef']]['userName'}

          return StreamBuilder<QuerySnapshot>(
            stream: firestore.collection('messages').snapshots(),
            builder: (BuildContext context,
                AsyncSnapshot<QuerySnapshot> MessagesSnapshot) {
              if (!MessagesSnapshot.hasData) return const Text('Loading...');
              final int messageCount = MessagesSnapshot.data.documents.length;
              return ListView.builder(
                itemCount: messageCount,
                itemBuilder: (_, int index) {
                  final DocumentSnapshot document =
                      MessagesSnapshot.data.documents[index];
                  // document['userRef'] exists here
                  // UserSnapshot[document['userRef']]['userName'} is accessible here
                  return ListTile(
                    title:
                        Text(document['message'] ?? '<No message retrieved>'),
                    subtitle: Text('Message ${index + 1} of $messageCount'),
                  );
                },
              );
            },
          );
        });
  }
}
like image 97
Robin Manoli Avatar answered Nov 15 '22 09:11

Robin Manoli