Flutter Firestore returns error without any problem in the code - flutter

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.

Related

StreamBuilder doesn't updates UI when Firestore data changes

My goal:
I want to retrieve a list of documents from the Firebase Firestore using a Stream to update the interface in real time when the Firestore data changes.
The problem:
I am able to download the data from Firestore with the structure I need, but when I make changes in firestore the interface does not update in real time. When I reload the page, it updates, but that is not the behavior I need.
This is the Stream I have created:
Stream<DayModel> getInstances(String selectedDay, String userUid) async* {
DayModel retval = DayModel();
List<InstanceModel> instances = [];
int index = 0;
try {
final QuerySnapshot<Map<String, dynamic>> querySnapshot =
await FirebaseFirestore.instance
.collection('instances')
.doc(selectedDay)
.collection('instancesUid')
.where("instanceUsersUid", arrayContains: userUid)
.get();
instances = querySnapshot.docs
.map((instance) => InstanceModel.fromSnapshot(instance))
.toList();
for (InstanceModel instance in instances) {
final DocumentSnapshot<Map<String, dynamic>> instanceQuery =
await FirebaseFirestore.instance
.collection('instances')
.doc(selectedDay)
.collection('instancesUid')
.doc(instance.uid)
.get();
instance = InstanceModel.fromMap(instanceQuery);
instances[index] = instance;
index++;
}
retval.instances = instances;
yield retval;
} on Exception catch (e) {
print(e);
}
}
StreamBuilder code:
body: StreamBuilder<DayModel>(
stream:
OurDatabase().getInstances(selectedDay, _currentUser!.uid!),
builder:
(BuildContext context, AsyncSnapshot<DayModel> snapshot) {
if (snapshot.hasError) {
return Center(child: CircularProgressIndicator());
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
return Center(
child: snapshot.data!.instances!.isNotEmpty
? Text(snapshot.data!.instances![0].uid!)
: Text('No tienes instancias!'),
);
})
Maybe it's because I'm not returning the Stream with a QuerySnapshot?
I have read in other similar posts that it could be a problem with the keys, but I have tried several different combinations and it has not worked.
Do you have any idea what could be happening?
Thank you for your time.

How to get values inside _JsonDocumentSnapshot?

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']

firestore doesnt show documents even though they are available

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'));

how to retrive value from a firestore flutter where query

I started flutter recently, and I try to retrieve the data from a query I made using 'where' , but the only thing I got back is "Instance of '_JsonQueryDocumentSnapshot'".
I tried different thing , but nothing work or i do it badly
this is my code :
CollectionReference users =
FirebaseFirestore.instance.collection('users');
final documents =
await users.where("username", isEqualTo: "username").get();
documents.docs.forEach((element) {
print(element);
});
I have also tried to use Future but without success :
class finduser extends StatelessWidget {
final String username;
finduser(this.username);
#override
Widget build(BuildContext context) {
CollectionReference users = FirebaseFirestore.instance.collection('users');
return FutureBuilder(
future: users.where('username', isEqualTo: '${username}').get(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasError) {
print("wrong");
return Text("Something went wrong");
}
if (snapshot.hasData && !snapshot.data!.exists) {
print("doesnt exist");
return Text("User does not exist");
}
if (snapshot.connectionState == ConnectionState.done) {
Map<String, dynamic> data = snapshot.data! as Map<String, dynamic>;
print(snapshot.data!);
return Text("${data}");
}
return Text("loading");
},
);
}
}
for the moment, all usernames are just "username"
Thank you for the help
When you get your documents like this :
CollectionReference users =
FirebaseFirestore.instance.collection('users');
final documents =
await users.where("username", isEqualTo: "username").get();
documents.docs.forEach((element) {
print(element);
});
You are trying to print an instance of a QueryDocumentSnapshot
This QueryDocumentSnapshot has a method .data() which returns a Map<String,dynamic> aka JSON.
So in order to print the content of your Document, do this :
documents.docs.forEach((element) {
print(MyClass.fromJson(element.data()));
});
This data by itself will not be very useful so I recommend creating a factory method for your class :
class MyClass {
final String username;
const MyClass({required this.username});
factory MyClass.fromJson(Map<String, dynamic> json) =>
MyClass(username: json['username'] as String);
}
Now you can call MyClass.fromJson(element.data()); and get a new instance of your class this way.
I have searched a lot but i see you have written code right.
The only thing that came to my mind is that you didn't initialize your firebase to your flutter project (you should do it in any flutter project to be able to use flutter).
link of the document:
https://firebase.flutter.dev/docs/overview#initializing-flutterfire
In your first code snippet you are printing element, which are instances of the QueryDocumentSnapshot class. Since you're not accessing specific data of the document snapshot, you get its default toString implementation, which apparently just shows the class name.
A bit more meaningful be to print the document id:
documents.docs.forEach((doc) {
print(doc.id);
});
Or a field from the document, like the username:
documents.docs.forEach((doc) {
print(doc.get("username"));
});
Run this code, it will work.
I also faced this similar problem, so I used this work around.
Map<String, dynamic> data = {};
FirebaseFirestore.instance.collection('users').where("username", isEqualTo: "username").get().then((QuerySnapshot querySnapshot) {
querySnapshot.docs.forEach((value){
data = value.data()!;
print('printing uid ${data['uid']}');
print('printing username--${data['username']}');
print('printing all data--$data');
});
});

How use the Current Id of an user in .doc()

I'm setting an User profile and I have a collection in my firestore which contain first 'users', in this I have the ID of this user and finally I can display name, uid etc.
The problem is I want to print that name in my front end but I can't access this collection because I don't know how to use the current uid in my .doc(uid), (uid is undefined), I used a future method to get the uid but I don't know how to connect these.
Hope you help me !
this is my frontend code
FutureBuilder(
future: FirebaseFirestore.instance
.collection('users')
.doc(uid)
.get(), //tryna to use that collection but uid is not defined correctly
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
var name = snapshot.data as DocumentSnapshot;
return Text(name['displayName'],
);
} else {
return Text("Loading...");
}
},
)
and my provider current User Id code
Future<String> inputData() async {
final User? user = _auth.currentUser;
final uid = user!.uid;
// here you write the codes to input the data into firestore
return uid;
}
I've updated frontend code here:
FutureBuilder(
future: FirebaseFirestore.instance
.collection('users')
.doc(FirebaseAuth.instance.currentUser!.uid)
.get(), //tryna to use that collection but uid is not defined correctly and I need a void but
// I have one in my auhtprovider
//solution ? create id from here : use my provider
builder: (context, snapshot) {
if (snapshot.hasData)
return Text("Loading...");
if (snapshot.data == null) {
print('Document does not exist on the database');
}else{
return Text("Researching data...");
}
if (snapshot.connectionState == ConnectionState.done) {
var name = snapshot.data as DocumentSnapshot;
return Text(name['displayName'],
);
} else {
return Text("Loading..");
}
},
)
and UID ?
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
Future userSetup(String displayName) async {
CollectionReference users = FirebaseFirestore.instance.collection('Users');
FirebaseAuth auth = FirebaseAuth.instance;
String uid = auth.currentUser!.uid.toString();
await users.doc(uid).set({'displayName': displayName, 'uid': uid });
final result = await users.doc(uid).get();
final data = result.data() as Map<String, dynamic>;
return data['displayName'];
}
Plus a photo of my firestore document :
To use the current user's UID in the read operation, do:
.doc(FirebaseAuth.instance.currentUser.uid)
The error you get after that, makes it sound like there is no document for the current user, which you code doesn't handle.
You'll need to check if (snapshot.hasData) before accessing snapshot.data, to ensure the AsyncSnapshot is done communicating with the database.
Then after that check if the document exists with if (snapshot.data!.exists) before accessing snapshot.data!.data().
Note that is pretty much exactly what the code in the documentation on reading data once does, so I highly recommend checking that out (again if needed).