The method 'data' was called on null - flutter

I made this function to get document(post)'s userId but docu always becomes null.
Is there any ideas to solve this problem?
(I use flutter and cloud firestore)
Future<String> getUserId(String text) async{
var docu;
await FirebaseFirestore.instance
.collection('post')
.where('content', isEqualTo: '$text')
.snapshots()
.listen((snapshot){
snapshot.docs.forEach((document) {
docu = document;
print('docUserId : ${document.data()['userId']}'); // this works well
});
});
return docu.data()['userId']; }
Also I tried return only document userId with String like this
Future<String> getUserId(String text) async{
String docUserId;
await FirebaseFirestore.instance
.collection('post')
.where('content', isEqualTo: '$text')
.snapshots()
.listen((snapshot){
snapshot.docs.forEach((document) {
docUserId = document.data()['userId'];
print('docUserId : ${docUserId}'); // this works well
});
});
return docUserId; }
Although print('docUserId : ${docUserId}'); this command works well, the final return value is always null.
I can't find the reason.

You need to take care of 3 importand points here:
You don't need to have a realtime listener if you just want to get the data once
You can't await a listener. Your function will always end before you get the data.
Check if a document exists before you use it
Could you pls try it with this code:
import 'package:cloud_firestore/cloud_firestore.dart';
Future<String> getUserId(String text) async {
String userUid = '';
QuerySnapshot docSnap = await FirebaseFirestore.instance
.collection('post')
.where('content', isEqualTo: '$text')
.get();
docSnap.docs.forEach((DocumentSnapshot document) {
if (document.exists) {
userUid = document.data()['userId'];
print('docUserId : ${document.data()['userId']}');
}
});
return userUid;
}

document.data()
change this to
docu = document;
print('docUserId : ${docu['userId']}');

Related

_TypeError (type 'Null' is not a subtype of type 'FutureOr<String>')

I am trying to fetch profile image from firestore. But it is giving an error.
Here is the code of fuction which is use to get the image from database. Kindly help if you can
Future<String> getUserImage() async {
final uid = auth.currentUser?.uid;
final users = await firestore
.collection("app")
.doc("user")
.collection("driver")
.doc(uid)
.get();
return users.data()?['dp'];
}
Your getUserImage method cant return null, you can return default value return users.get('dp')?? "got null";
or accept nullable data
Future<String?> getUserImage() async {
final uid = auth.currentUser?.uid;
final users = await firestore
.collection("app")
.doc("user")
.collection("driver")
.doc(uid)
.get();
return users.get('dp');
}
Try the following code:
Future<String> getUserImage() async {
final String uid = auth.currentUser!.uid;
final DocumentSnapshot<Map<String, dynamic>> users = await firestore
.collection("app")
.doc("user")
.collection("driver")
.doc(uid)
.get();
return users.get('dp');
}

Collecting string from Firebase Firestore in flutter

I am creating like system and i want to get likeCount from firebase which i created.
It's collecting it but returns null,
here is my code:
String? getLikecount(tresc) {
String? likeCount;
FirebaseFirestore.instance
.collection('Posty')
.where('Tresc', isEqualTo: tresc)
.get()
.then((value) => value.docs.forEach((element) async {
var id = element.id;
final value = await FirebaseFirestore.instance.collection('Posty').doc(id).get();
likeCount = value.data()!['likeCount'].toString();
print(likeCount);
}));
print(likeCount);
return likeCount;
}
and here is console output:
Data is loaded from Firestore (and most modern cloud APIs) asynchronously, because it may needs to come from the network and we can't block your code (and your users) while waiting for it.
If we change the print statements a bit, and format the code, it'll be much easier to see what's going on:
String? getLikecount(tresc) {
String? likeCount;
FirebaseFirestore.instance
.collection('Posty')
.where('Tresc', isEqualTo: tresc)
.get()
.then((value) => value.docs.forEach((element) async {
var id = element.id;
final value = await FirebaseFirestore.instance
.collection('Posty')
.doc(id)
.get();
likeCount = value.data()!['likeCount'].toString();
print('In then: $likeCount');
}));
print('After then: $likeCount');
return likeCount;
}
If you run this, you'll see it outputs:
After then: null
In then: 0
This is probably not what you expected, but it explains perfectly why you don't get a result. By the time your return likeCount runs, the likeCount = value.data()!['likeCount'].toString() hasn't executed yet.
The solution is always the same: any code that needs the data from the database has to be inside the then handler, be called from there, or be otherwise synchronized.
In Flutter it is most common to use async and await for this. The key thing to realize is that you can't return something now that hasn't been loaded yet. With async/await you function becomes:
Future<String?> getLikecount(tresc) {
String? likeCount;
var value = await FirebaseFirestore.instance
.collection('Posty')
.where('Tresc', isEqualTo: tresc)
.get();
for (var doc in value.docs) {
var id = element.id;
final value = await FirebaseFirestore.instance
.collection('Posty')
.doc(id)
.get();
likeCount = value.data()!['likeCount'].toString();
print('In then: $likeCount');
}));
print('After then: $likeCount');
return likeCount;
}
Now your code returns a Future<String?> so a value that at some point will hold the string. When calling getLikecount you will now need to use then or await to handle the Future, and if you want to show the count in the UI you will have to store it in the State of a StatefulWidget.

Function always return false even with a valid ID

I have this code to check if a gameId exists on Firestore. Even when I provide a valid gameId, it returns false although the name of the player is actually printed to the console!
Future<bool> checkTwoPlayerCode(String gameId) async {
final document = _tictactoeCollection.doc(gameId);
await document.get().then((snapshot) {
if (snapshot.exists) {
print(snapshot.get('playerOne'));
return true;
}
});
return false;
}
With the help of Jack's answer to another post, this solves the issue I was having:
Future<bool> checkTwoPlayerCode(String gameId) async {
final document = _tictactoeCollection.doc(gameId);
return await document
.get()
.then((DocumentSnapshot snapshot) => snapshot.exists);
}

How to get firestore data as stream?

I am making a collection group query, where upon matching a particular field, I am going a level backwards and then read the data.
I am able to do that in Future approach.
Here is my code returning future:
#override
Future<Either<JobPostFailure, List<JobPost>>> readAppliedJobPosts({
required String seamanId,
}) async {
final querySnapshot = await FirebaseFirestore.instance
.collectionGroup(ConstStrings.applications)
.where(
ConstStrings.seamanId,
isEqualTo: seamanId,
)
.get();
final List<JobPost> mList = [];
for (var docSnap in querySnapshot.docs) {
final jobPostDocSnap = await docSnap.reference.parent.parent?.get();
mList.add(JobPostDto.fromFirestore(jobPostDocSnap!).toDomain());
}
return right(mList);
}
Now I am struggling to do this in Stream approach, where my return type would be something like this : Stream<Either<JobPostFailure, List<JobPost>>>. What is the equivalent of my above code in Stream?
My try so far :
#override
Stream<Either<JobPostFailure, List<JobPost>>> watchAppliedJobPosts({
required String seamanId,
}) async* {
yield* _firestore
.collectionGroup(ConstStrings.applications)
.where(
ConstStrings.seamanId,
isEqualTo: seamanId,
)
.snapshots()
.map((event) {
return event.docs.map((e) {
return e.reference.parent.parent!.snapshots().map((event) {
return right(JobPostDto.fromFirestore(event).toDomain());
}).toList();
});
});
}
And its a big mess!
You can use method snapshots instead of get. Is will create a new stream that will fetch data for every change your document or collection has

I need some guidance in the Future Asynchronous Calls with flutter and dart, sometimes things happen out of order

The following code works fine, because it return only a simple list, but in some cases that I need to do nested Firebase calls, I can't make things happen in the right order, and the main return statement comes incomplete. What can I do to improve my Future Asynchronous Calls?
Future<List<MyNotification>> getNotifications() async {
var uid = await FirebaseAuth.instance.currentUser();
List<MyNotification> tempNots = await Firestore.instance
.collection("notifications")
.where("targetUsers", arrayContains: uid.uid)
.getDocuments()
.then((x) {
List<MyNotification> tempTempNots = [];
if (x.documents.isNotEmpty) {
for (var not in x.documents) {
tempTempNots.add(MyNotification.fromMap(not));
}
}
return tempTempNots = [];
});
return tempNots;
}
The most important thing; don't use then inside your async functions. I modified your code like this;
Future<List<MyNotification>> getNotifications() async {
// Using the type definition is better.
FirebaseUser user = await FirebaseAuth.instance.currentUser();
// The return type of getDocuments is a QuerySnapshot
QuerySnapshot querySnapshot = await Firestore.instance
.collection("notifications")
.where("targetUsers", arrayContains: user.uid)
.getDocuments();
List<MyNotification> tempTempNots = [];
if (querySnapshot.documents.isNotEmpty) {
for (DocumentSnapshot not in querySnapshot.documents) {
tempTempNots.add(MyNotification.fromMap(not));
}
}
return tempTempNots;
}