Flutter & Firebase: Return list of collections

Viewed 2187

Can't find any information on this, because the answer already on here to use "listCollections()" or "getCollections()" are not even valid methods.

I have a database set up like so:

Layout:  Collection -> Document -> Collection -> Documents
  Example Data:
  Stores  -> UID  -> StoreName1 -> 
                     StoreName2 -> 
                     StoreName3 -> 

And so given that I know Stores and the UID, I should easily be able to return all the store names above. There has to be a line of code that will give me the results: StoreName1, StoreName2 & StoreName3.

Here is what I have:

CollectionReference documents = (await Firestore.instance.collection('Stores').document(widget.currentUserUID).    <---- now what?   There is no getCollections, there is no listCollections.   How can I return this information?

Edit

So after discussing this with Peter below I have tried to work through an adjusted approach but it seems horrifically messy. Is there a better way of doing this? Feels wrong. Let me start with the new design:

Layout:  Collection -> Document -> Collection -> Documents
  Example Data:
  Stores  -> UID  -> StoresList -> Store1
                                    store_name: 'My Place'
                                   Store2
                                    store_name: 'Bellagio'
                                   Store3
                                    store_name: 'Grand Hotel'

So you can see that Store1, Store2 and Store3 above are documents and that they have fields, one of which is store_name.

I am trying to present the user with a list of the Stores they have under their account. The way I am having to do this, not knowing how many stores each user may want to operate, is as follows

storeCount == 0 ? SizedBox()
              :
storeCount == 1 ? Container(
  child: uid == null ? LoadingAnimation() : StreamBuilder(
  stream: Firestore.instance.collection('stores').document(uid).collection('StoresList').document('Store1').snapshots(),
  builder: (BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
    if (!snapshot.hasData) {
      return LoadingAnimationBasic();
    }
    if (snapshot.data.data == null) {
      return LoadingAnimationBasic();
    } else {
      return ListView.builder(
    shrinkWrap: true,
      itemCount: 1,
      itemBuilder: (context, index) =>
        _buildStoresList(context, snapshot.data),
       );
     }
   },
 )) :
storeCount == 2 ? Container(
  child: uid == null ? LoadingAnimationBasic() : StreamBuilder(
  stream: Firestore.instance.collection('stores').document(uid).collection('StoresList').document('Store1').snapshots(),
  builder: (BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
    if (!snapshot.hasData) {
      return LoadingAnimationBasic();
    }
    if (snapshot.data.data == null) {
      return LoadingAnimationBasic();
    } else {
      return ListView.builder(
        shrinkWrap: true,
        itemCount: 1,
        itemBuilder: (context, index) =>
          _buildStoresList(context, snapshot.data),
        );
      }
    },
  ),
Container(
  child: uid == null ? LoadingAnimationBasic() : StreamBuilder(
  stream: Firestore.instance.collection('stores').document(uid).collection('StoresList').document('Store2').snapshots(),
  builder: (BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
    if (!snapshot.hasData) {
      return LoadingAnimationBasic();
    }
    if (snapshot.data.data == null) {
      return LoadingAnimationBasic();
    } else {
      return ListView.builder(
        shrinkWrap: true,
        itemCount: 1,
        itemBuilder: (context, index) =>
          _buildStoresList(context, snapshot.data),
        );
      }
    },
  ),
) : SizedBox(),

And then the _buildStoresList function:

  Widget _buildStoresList(BuildContext context, DocumentSnapshot document) {
    storeName = document['store_name'];
    return Center(
      child: Text(
        storeName,
        style: TextStyle(
          fontFamily: 'Petita',
          color: Colors.black,
          fontSize: 60,
        ),
      ),
    );
  }

So basically, first counting how many stores a user has (by counting the number of documents in the StoresList collection during initState() using a Future.delayed workaround to populate this correctly). Once I have the number of stores, the only way I can see to get the data from each document is to do that above with horrible nested if statements.

What have I got wrong here? Or is this the only way to do it?

I guess the main question is regarding this line:

stream: Firestore.instance.collection('stores').document(uid).collection('StoresList').document('Store1').snapshots(),

Where I need to give a specific document in order to snapshot the fields. It would be infinitely more efficient to have a line that took the snapshot of all documents in the collection and not just one at a time like this. Otherwise if the user wants to manage 200 stores, this 'if statement' nest is going to be insane.

2 Answers

You need to change the database to the following:

Stores (Collection) --> UID (Document) --> Stores (Collection) --> StoreName_1 (Document) 
                                                                   StoreName_2 (Document)

Then you can do:

var documents = await Firestore.instance.collection('Stores').document(widget.currentUserUID).collection("Stores").getDocuments();

This will get you all the Stores under one user.

(Posted an answer on behalf of the question author, in order to move it to the answer space).

I have worked this out, it is much simpler than nesting if statements like I was originally trying.

So the database layout is exactly the same, and you can only pull data back out of documents, not collections, so make sure you structure your databases with this in mind.

You can return all the documents from a collection like this:

Container(
  child: StreamBuilder(
    stream: Firestore.instance.collection('stores').document(uid).collection('StoresList').snapshots(),
    builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
      if (!snapshot.hasData) {
        return LoadingAnimationBasic();  //This is a custom animation, you can use SizedBox() or anything you like here.
      }
      if (snapshot.data == null) {
        return LoadingAnimationBasic();
      } else {
        return ListView(
          shrinkWrap: true,
          children: _buildStoresList(snapshot),  //Here we are chucking the whole collection snapshot into a ListView builder.  This sends all the documents in the collection in this direction.
        );
      }
    },
  ),
),

And then the function that handles all those documents is pretty straight-forward:

  _buildStoresList(AsyncSnapshot<QuerySnapshot> snapshot) {
    return snapshot.data.documents
        .map((doc) => ListTile(
          title: Center(child: Text(doc['store_name'])),   //store_name is one of the fields inside my documents inside the collection on which I took the snapshots.
          subtitle: Center(child: Text(doc['about']))  //about is another such field.
    ))
        .toList();
  }

That's it.

Related