Flutter Streambuilder code below runs without error and returns (screenshot at bottom):
ID: AzFdOO9WsFaFbTxTQsuo
Data: Instance of '_JsonDocumentSnapshot'
How do I get to the values inside the _JsonDocumentSnapshot and display them in the Text() widget?
For instance, there's a string field called "name", but I can't figure out how to get to it.
StreamBuilder(
stream: FirebaseFirestore.instance
.collection("groceries")
.doc(widget.docId)
.snapshots(),
builder: (context, streamSnapshot) {
if (streamSnapshot.connectionState == ConnectionState.waiting) {
return const Text("Loading");
} else if (streamSnapshot.hasData) {
return Text("ID: ${widget.docId}\n"
"Data: ${streamSnapshot.data}");
} else {
return const Text("No Data");
}
}
)
Thanks for your help!
the following Stream, return an object with a type of DocumentSnapshot :
FirebaseFirestore.instance.collection("groceries").doc(widget.docId).snapshots();
and that type contains the document snapshot, and also contains more additional information about the document.
so in order to get the JSON Map<String, dynamic> which represents the data of the Firestore document, you need to call data() on the result of the snapshot.data(), so you need to try the following:
StreamBuilder<DocumentSnapshot>(
stream: FirebaseFirestore.instance
.collection("groceries")
.doc(widget.docId)
.snapshots(),
builder: (BuildContext context, AsyncSnapshot<DocumentSnapshot> streamSnapshot) {
if (streamSnapshot.connectionState == ConnectionState.waiting) {
return const Text("Loading");
} else if (streamSnapshot.hasData) {
return Text("ID: ${widget.docId}\n"
"Data: ${streamSnapshot.data.data()}"); // added data()
} else {
return const Text("No Data");
}
}
)
now it should show the Map<String, dynamic> object which contains your document data in the Text widget.
hope this helps.
In your code example streamSnapshot.data is an Object or a dynamic type variable.
To access the json value of your data, you have to specify the key corresponding to your value.
streamSnapshot.data['banana']
Related
Using Flutter 3.3.9, I fetch a record from my Firestore database using a Streambuilder. I use the following code segment to do this:
StreamBuilder<Object>(
stream: FirebaseFirestore.instance
.collection('users')
.doc(userId)
.snapshots(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Text('Loading...');
}
return Text(
snapshot.data!.doc['username'], // This line is marked as error bexcause "doc" is illegal.
),
);
},
),
The snapshot.data!.doc['username'] gives the following error:
The getter 'doc' isn't defined for the type 'Object'.
I verified that the 'Object' is of type "AsyncSnapshot" (=snapshot.runtimeType). It looks like the only getters available for the snapshot.data are hashCode, runtimeType, toString(), and noSuchMethod(..).
I tried
snapshot.data!().doc['username'],
But this does not work either. The error is "The expression doesn't evaluate to a function, so it can't be invoked"
I was able to access the data without using the StreamBuilder. The following works:
final docRef = FirebaseFirestore.instance
.collection('users')
.doc(userId);
docRef.get().then(
(DocumentSnapshot doc) {
final data = doc.data() as Map<String, dynamic>;
print(data['username']);
},
onError: (e) => print("Error getting document: $e"),
);
you have two mistakes, in your piece of code, you should specify the type of the AsyncSnapshot, like this:
StreamBuilder<DocumentSnapshot>( // Specified type
stream: FirebaseFirestore.instance
.collection('users')
.doc(userId)
.snapshots(),
builder: (context, AsyncSnapshot<DocumentSnapshot> snapshot) { //Specified type
//...
now using snapshot.data!, it should be a DocumentSnapshot type, and as I see, that you're trying to get the data of that document so you need also to change this line:
snapshot.data!.doc['username'],
to this:
(snapshot.data!.data() as Map<String, dynamic>)['username'],
now it will access the username field properly.
You define your StreamBuilder's type in wrong way, change it to this:
StreamBuilder<AsyncSnapshot>(
...
)
I have following code to add data to firebasefirestore
Future<void> sendMessage({
required String msg,
required String id,
}) async {
var docId = getDocId(id); // returns sth like "AbcDe-FghiJ"
DocumentReference documentReferencer = chat.doc(docId).collection('chatMsg').doc();
Map<String, dynamic> data = <String, dynamic>{
"message": msg,
"sentBy": ownId,
"sentAt": DateFormat('yyyy-MM-dd – kk:mm:ss').format(DateTime.now())
};
await documentReferencer.set(data);
}
I used following code to get the data
StreamBuilder<QuerySnapshot>(
stream: firebaseInstance.collection('Messages').snapshots(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasError || !snapshot.hasData) {
return const Center(
child: CircularProgressIndicator()
);
} else {
var data = snapshot.data.docs;
return listBuilder(data);
}
}
)
listBuilder(listData) {
return ListView.builder(
shrinkWrap: true,
itemCount: listData.length,
itemBuilder: (BuildContext context, int index) {
return Text(listData[index].id);
}
)
}
However, data show 0 items even though there is a document present.
My question is how can I get the list of documents from Messages?
I was having the same exact problem with subcollections on Firestore and even asked a question here to get some help over it. Though, it seems like the snapshots won't show the documents having a subcollection in them as there is no field inside any of them. So what I did to counter this was to just add anything (just a random variable) and then it was able to find the documents.
This is my current layout:
I've just added another line of code to just add this whenever I'm inserting a new subcollection.
collection
.set({
'dummy': 'data'
})
.then((_) => print('Added'))
.catchError((error) => print('Add failed: $error'));
The following code returns error "NoSuchMethodError"
StreamBuilder(
stream: SalaryService.getSingle(),
builder: (_, snapshot) {
if (snapshot.data() != null) {
print('step 3');
return Text(
snapshot.data['value'].toString(),
);
} else {
return Text(
"Nil",
);
}
},
),
class SalaryService {
static Stream<DocumentSnapshot> getSingle() {
Stream<DocumentSnapshot> snapshot = FirebaseFirestore.instance
.doc(userId + '/salary' + todayYM)
.snapshots();
snapshot.forEach(
(element) {
// prints all the documents available
// in the collection
print(element.data().toString());
// print((element.data() != null).toString());
},
);
return snapshot;
}
}
The cloudstore document does not exist to begin with until the user updates his salary hence the if else used.
P.S.: I am a rookie
Two things:
It is best practice to check whether the QueryDocumentSnapshot returned has data, plus check whether the document reference exists first by casting it as a DocumentSnapshot as opposed to pull the data straight up, as in:
if (snapshot.hasData && (snapshot.data as DocumentSnapshot).exists) { // ... }
you cannot pull the properties out of the snapshot like snapshot.data['field'] without pulling the data out first as a Map<String, dynamic>; you at least have to do first is snapshot.data() (after checking that it exists), then pull the fields out of the returned map, as in:
Map<String, dynamic> docData = (snapshot.data as DocumentSnapshot).data() as Map<String, dynamic>;
print(docData['value']);
Check this Gist for the full code (replace with your Firebase config settings at the top if you want to test it by running it on DartPad.dev.
I'm expecting my random value to be an int but I got a String instead,
is this the right way to do it.
StreamBuilder(
stream: FirebaseDatabase.instance.ref().child('RandomVal').onValue,
builder: (context, snapshot) {
if (snapshot.hasData && !snapshot.hasError) {
final event = snapshot.data as DatabaseEvent;
final data = event.snapshot.value as Map;
print(data['Value']); // my value as expected
print(data['Value'].runtimeType); // String instead of int
}
return Text('please wait');
},
),
full error message:
The following JSNoSuchMethodError was thrown building FutureBuilder<DocumentSnapshot>(dirty, state: _FutureBuilderState<DocumentSnapshot>#dfc82):
NoSuchMethodError: invalid member on null: '_get'
it comes from this line : UserModel user = UserModel.fromDoc(snapshot.data); and it is in :
body: FutureBuilder(
future: usersRef.doc(widget.userId).get(),
builder: ( context, snapshot) {
List<Widget> children;
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
else if (snapshot.hasData) {
// print('user snapshot data is: ${snapshot.data}');
UserModel user = UserModel.fromDoc(snapshot.data);
model looks like :
factory UserModel.fromDoc(DocumentSnapshot doc) {
return UserModel(
id: doc.data()['id'],
name: doc.data()['name'],
username: doc.data()['username'],
password: doc.data()['password'],
profileImageUrl: doc.data()['profileImageUrl'],
email: doc.data()['email'] ,
userIds: doc.data()['userIds'] ?? '',
);
}
I tried downgrading the version of cloud_firestore but still don't work
As your error log tells, you are accessing some value on a null member.
It seems, the error lies in your factory method. In Flutter, to access all data from documentSnapshot in Map<String, dynamic> have to use doc.data.
Before doing that, we could check for the document existence within DocumentSnapshot using doc.exists. For further ref - https://firebase.flutter.dev/docs/firestore/usage/
I prefer to handle all connectionstate including error or else your screen would get stuck in CircularProgressIndicator and it's hard for user to know the reason.
if (snapshot.hasError) {
return Text("Something went wrong");
} else if (snapshot.connectionState == ConnectionState.done) {
Map<String, dynamic> data = snapshot.data.data;
return Widget;
} else {
return Center(
child: CircularProgressIndicator());
}
Another case: If no document exists in firestore, the read (snapshot.data) will return null. However this null case internally handled by the futurebuilder connectionState. As per your debug result, since the snapshot.data has DocumentSnapshot, it didn't cause the error.