2

I am building a nutrition app for practice.

Right now I got a collection named "inventar" which stores a document, containing two arrays. "product" is an array which contains all products, like milk, sausage, cheese etc. Another array, "expirationDate" contains, when the products are going to decay.

I would like to show the content of these two arrays within a table.

Sounds easy, right?

Well, I am struggling with this exercise since over 2 weeks and am stuck, so I am asking for your help.

First, I asked for the data in multiple ways, until I became totaly confused about DocumentSnapshot, QuerySnapshot, DocumentReference, AsynchSnapshot and when to use get(), snapshot() or StreamBulder()/FutureBuilder() right now. I feel like, every time I found a reasonable solution in the internet, one little tiny thing doesnt work out, leading to fail the entire approach and having to search for another way.

Searching the net for ways to query an array, which should be pretty easy, I am only finding solutions, performing a search like "give me only those entrys". I simple want to get the entire array out, no entrys from x to y (at least not right now). Or can't I query an array whichout a where() statement?

After I did not find a way which worked out for me, I tried to get the array length after my "query", so that I could do a for-loop to retrive the data. BUT I did not seem to find the length property of the query to deliver it to the for-loop.

So, after this approach, I tried to work with StreamBuilder and FutureBuilder and a seperate async method. BUT either the FutureBuilder complains about me not providing the proper Widget type (how to return a new TableRow from a TableRow type method, if there is a Future involved?). StreamBuilder did not work out for me neither.

I don't even have code to share with you right now, because I threw every approach away in hope the next one would hopefully work. I guess, there is alot of confusion to me right now because I dont see how the individuell parts of the firestore query stick together and when I have to/can use get(), when snapshot() when map() and so on.

So, in short terms: I simply would like to retrive two seperate arrays within one doc within on collection and to put these into a text widget within a TableCell.

How would you guys do this?

Would appreciate if you could explain to me when to use StreamBuilder() and FutureBuilder() and when/in which order to use get(), snapshot(), map() related to the firestore query. I am so confused right now.

Looking forward to your replys!

Goku

Edit:

So, this is what I got from the link Frank send:

            new StreamBuilder<QuerySnapshot>(
                stream: FirebaseFirestore.instance.collection("inventar").snapshots(),
                builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
                  if (snapshot.hasError) {
                    return Text('Something went wrong');
                  }

                  if (snapshot.connectionState == ConnectionState.waiting) {
                    return Text("Loading");
                  }

                  return new ListView(
                    children: snapshot.data!.docs.map((DocumentSnapshot document) {
                      return new ListTile(
                        title: new Text(document.data()?['produkt']),
                        subtitle: new Text(document.data()?['verfallsdatum']),
                      );
                    }).toList(),
                  );
                },
              )

Right now I get "type 'List' is not a subtype of type 'String'", and that even though I am actualy calling for strings in an array. Tried to use .toString() but then it says "The argument type 'String?' can't be assigned to the parameter type 'String'".

And, don't I need an Index value to be able to call each individual entry in the array? Or is this done automaticaly using .map()?

Not to mention I set the date as string currently, because I already did research on transfering the firebase timestamp to an actual date which looked really complicated. So I posponed this until I got the data in the right structure first.

Goku
  • 49
  • 7
  • Instead of describing everything you've tried in words, can you edit your question to show one approach that you think should've worked as actual code? Seeing such a [minimal reproduction](http://stackoverflow.com/help/mcve) will make it much more like that someone here can help beyond what the documentation shows here: https://firebase.flutter.dev/docs/firestore/usage#realtime-changes – Frank van Puffelen May 10 '21 at 17:03
  • Thanks for your reply, Frank. I just edited my post. Hope it's not too confusing. Looking forward to your help/explainations. Im pretty sure you will be able to bring light in the dark. – Goku May 11 '21 at 17:00

1 Answers1

0

You are storing 'product' and 'expirationDate' array in a single document. But in your code i can see that you are trying to fetch documents from that query of 'inventar'. Thats why you getting "type 'List' is not a subtype of type 'String'" error because

document.data()?['produkt']

this will return a list. And you are trying to give that list to a Text widget which only accepts a string. What you should have done was to fetch only a single document and then design a iterable widget which will go through the list inside your document. You have done opposite, you are iterating before your document is fetched.

Lets do the way i have just explained that is iterating after fetching document-

Im following the first paragraph of what your question says and writing the code now-

StreamBuilder<QuerySnapshot>(
            stream: FirebaseFirestore.instance.collection("inventar").snapshots(),
            builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
              if (snapshot.hasError) {
                return Text('Something went wrong');
              }

              if (snapshot.connectionState == ConnectionState.waiting) {
                return Text("Loading");
              }

              return new ListView(
                children: snapshot.data.docs.first.data()['products'].map((String product){
                  return ListTile(leading: Text(product));
                }).toList()
              );
            },
          )

In your code you are fetching list of documents which is not the case as you explained how your database looks. As you can see in my code, we can only fetch either product or expirationDate because they are stored in seperate list. What i have done in this code, is that i went to the inventar collection and fetched a querysnapshot of that collection which is fetched by this code-

stream: FirebaseFirestore.instance.collection("inventar").snapshots()

Now this QuerySnapshot contains all documents in that collection. As you said you have only one document in your collection, so i have fetched in that way-

snapshot.data.docs.first.data()['products']

now this will return a list, because you have stored a list in that document. So i have used map function to map the product list into a ListTile widget and return it. So this way we can only fetch one list in stream builder, either products or expirationDate. If you dont want your database to be changed, then you have to fetch those two list seperately using a function(not streambuilder). But there is another way we can fetch your data the way you want without changing your database structure. By using a listview.builder widget-

StreamBuilder<QuerySnapshot>(
  stream: FirebaseFirestore.instance.collection("inventar").snapshots(),
  builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
    if (snapshot.hasError) {
      return Text('Something went wrong');
    }

    if (snapshot.connectionState == ConnectionState.waiting) {
      return Text("Loading");
    }

    return new ListView.builder(
      itemCount: snapshot.data.docs.first.data()['products'].length,
      itemBuilder: (ctx, i) {
        return ListTile(
          leading: Text(snapshot.data.docs.first.data()['products'][i]),
          trailing:
              Text(snapshot.data.docs.first.data()['expirationDate'][i]),
        );
      },
    );
  },
)

Here the length of listview.builder widget will be given by itemCount. So im assuming that both the list having the same length otherwise it will throw errors. This will return a list-

snapshot.data.docs.first.data()['products']

And then this will return its length-

snapshot.data.docs.first.data()['products'].length

Suppose the value of length of your list is 5. Now listview.builder widget will automatically return listTile widget 5 number of times and i is index which goes from 0 to 4 inclusive. So now inside ListTile you can show the ith data of your list.

So i guess this code should work. And i hope you get the idea of how im fetching the list first and then iterating.

Artisted
  • 144
  • 1
  • 10
  • Good morning and thank you for your reply, Artisted! Makes total sense to me. But how may to this with a table I actualy want to display my data in? – Goku May 12 '21 at 06:25
  • If you are able to fetch data correctly, then ListTile is good for showing your data. Or maybe you can create your own custom Widget to do so. There is a Table widget also. Follow this link - https://api.flutter.dev/flutter/widgets/Table-class.html – Artisted May 13 '21 at 08:15
  • This link also - https://www.javatpoint.com/flutter-table – Artisted May 13 '21 at 08:21