Flutter Firestore getDocuments() not working on initState - flutter

I'm having an issue trying to call a document from Firestore in initState. I can pull data from a StreamBuilder just fine, but I just want to call the data once on initState.
Here is what I'm doing:
class _PainLevelState extends State<PainLevel> {
final FirebaseAuth _auth = FirebaseAuth.instance;
final CollectionReference userCollection =
Firestore.instance.collection('users');
static final now = DateTime.now();
String _uid;
double myPainLevel = 0;
Future<void> getCurrentUser() async {
final FirebaseUser user = await _auth.currentUser();
if (mounted) {
setState(() {
_uid = user.uid;
});
}
}
Future<void> getCurrentPainLevel() async {
await userCollection
.document(_uid)
.collection('pain')
.where('time',
isGreaterThanOrEqualTo: DateTime(now.year, now.month, now.day))
.getDocuments()
.then((QuerySnapshot docs) {
if (docs.documents.isNotEmpty) {
print('yes');
} else {
print('no');
}
});
}
#override
void initState() {
super.initState();
getCurrentUser();
getCurrentPainLevel();
}
...
I just get a "no" every time I print to console. It's not get any documents when there is one. If I take the same code inside the future and put it somewhere else, like in the build method, it works, but it constantly builds and I don't want that. Any suggestion as to why it is not working? Thanks in advance.

I'm guessing here that your code will not always work, because getCurrentPainLevel might get called before getCurrentUser is completed, which will cause _uid to be null and therefore, not work as expected. Try to put then keyword after getCurrentUser method, like this:
#override
void initState() {
super.initState();
getCurrentUser().then((_) {
getCurrentPainLevel();
});
}
By the way, you should NOT be calling async methods in initState. Try to put the code somewhere else, like WidgetsBinding.instance.addPostFrameCallback(...).

Related

Flutter Using async await requires hotreload but using .then doesn't

Can anyone help me understand this piece of code:
String? userName = "";
String? userEmail = "";
AuthService authService = AuthService();
#override
void initState() {
// TODO: implement initState
super.initState();
gettingUserData();
}
while defining gettingUserData(), using async, await needs hotreload to show the email
gettingUserData() async {
setState(() async {
userName = await HelperFunction.getUsername();
userEmail = await HelperFunction.getUseremail();
});
}
But defining it using .then doesn't need hot relaod
gettingUserData() {
HelperFunction.getUseremail().then((value) {
setState(() {
userEmail = value;
});
});
HelperFunction.getUsername().then((value) {
setState(() {
userName = value;
});
});
}
Can anyone help me understand why this is?
The two versions are not equivalent. The Future.then version calls setState after each Future completes.
The await version calls setState with an asynchronous callback, which setState does not expect. Since setState expects a VoidCallback argument, it expects its callback to complete synchronously, and it will not be awaited. setState therefore executes and returns immediately before waiting for either of the Futures complete.
One way to correct your await version is to await the Futures first and to then call setState:
Future<void> gettingUserData() async {
var userName = await HelperFunction.getUsername();
var userEmail = await HelperFunction.getUseremail();
setState(() {
this.userName = userName;
this.userEmail = userEmail;
});
}

why it is showing instance of future? how to get data

Future<void> setEmpId(String empId) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString(this.empId, empId);
}
Future<String> getEmpId() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String empId;
empId = await prefs.getString(this.empId) ?? '';
return empId;
}
Prefs().setEmpId(state.empVerifyEntity.employee.empId);//set empId from api
In Another Class:
class Page extends State<Page>{
Future<void> getEmpId() async {
String empId = await Prefs().getEmpId().toString();
print("----------->>>>>>>>$empId");
}
#override
void initState() {
super.initState();
getEmpId();
}
}
Here I'm getting instance of future, I tried every method like .then(value) Each and every method I'm getting instance of future. how to data correctly?
The problem you have is due to the fact that your initState method is synchronous and the method in which you are getting the value for EmpId isn't.
Therefore, it is not waiting for the result of the call.
You can accomplish this in several ways:
Add a then clause to the call of getEmpId
#override
void initState() {
super.initState();
getEmpId().then((result) {
//your logic here
)
}
Add a PostFrameCallback
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_){
getEmpId();
});
}

Why am I getting 'Future<dynamic>' instead of the return value in the function?

I'm trying to get the return value in my function but the output is 'Instance of Future' instead of the value of school field name in the database
#override
void initState() {
userId = _auth.currentUser!.uid;
publisherSchool =
getName(widget.postInfo['publisher-Id'], 'school').toString();
super.initState();
}
Future getName(String publisherUid, String fieldname) async {
DocumentSnapshot publisherSnapshot = await FirebaseFirestore.instance
.collection('users')
.doc(publisherUid)
.get();
print(publisherSnapshot.get(fieldname));
return publisherSnapshot.get(fieldname);
}
but whenever i'm printing the publisherSnapshop.get(fieldname) i'm getting the correct value from the database
There are 2 ways to do it, you can create a Future method and call it inside the initState like below:
#override
void initState() {
initial();
super.initState();
}
Future<void> initial() async {
userId = _auth.currentUser!.uid;
// Remember using `()` to wrap the `await` to get it result
publisherSchool = (await getName(widget.postInfo['publisher-Id'], 'school')).toString();
}
Or you can use .then to call it directly inside the initState:
#override
void initState() {
userId = _auth.currentUser!.uid;
getName(widget.postInfo['publisher-Id'], 'school').then((value) {
publisherSchool = value.toString();
});
super.initState();
}
When you declare the getName() function, specify the return type as Future<String>, and then when you call getName(), you need to await the result e.g. publisherSchool = await getName(widget.postInfo['publisher-Id'], 'school').toString();
The reason why you are not getting the correct response is because whenever you are working with Futures it takes some time to finish and return the results. Meanwhile it is fetching the result you have to make it await so that the program will continue once that future function is complete since await/then is nowhere to be found in your code hence the issues.
To solve this make this change:
Change
publisherSchool =
getName(widget.postInfo['publisher-Id'], 'school').toString();
To
getName(widget.postInfo['publisher-Id'],
'school').then((value){
publisherSchool=value.toString()});

DocumentSnapshot Listener

I have a question how do i listen to real-time changes to a document snapshot based on the email of the currentUser in a initState. I knew that QuerySnapshot will need to use documents.forEach to be able to retrieve but how about DocumentSnapshot. This is my code:
#override
void initState() async{
FirebaseUser user = await FirebaseAuth.instance.currentUser();
Firestore.instance.collection('user').document(user.email).snapshots()
.listen((userinfo){
userinfo((userI){
_fname = userI.data['FullName'];
_SSID = userI.data['SSID'];
_email = userI.data['email'];
_image = userI.data['image'];
});
});
super.initState();
}
I am able to get the user.email but not able to listen to the Firestore. I can't really find anything that is similar to this maybe it's a relatively easy problem to solve for I'm still new to Flutter and Firebase. Thank you in advance.
It should be userI.data()['some_key'] instead of userI.data['some_key'].
https://pub.dev/documentation/cloud_firestore/latest/cloud_firestore/DocumentSnapshot/data.html
Also reomve userInfo, it should be like this:
Firestore.instance.collection('user').document(user.email).snapshots().listen((userI) {
_fname = userI.data()['FullName'];
_SSID = userI.data()['SSID'];
_email = userI.data()['email'];
_image = userI.data()['image'];
});
You can move your asynchronous logic into a different function and call it in your initState like this:
#override
void initState() {
super.initState();
_getUserData();
}
Future<void> _getUserData() async {
FirebaseUser user = await FirebaseAuth.instance.currentUser();
Firestore.instance.collection('user').document(user.email).snapshots()
.listen((userinfo) {
setState((){
_fname = userinfo.data['FullName'];
_SSID = userinfo.data['SSID'];
_email = userinfo.data['email'];
_image = userinfo.data['image'];
});
});
}
And in the widget where you use the variables, you can easily add a null check and display an empty value or a loading indicator.
Text(_fname??'')
or
_fname == null ? Center(child: CircularProgressIndicator()) : Text(_fname)

How can i always setState when the page is opened?

My issue is, when the page is opened my variable isn't set. So when i'm querying from firestore i get the error The method '[]' was called on null. Receiver: null Tried calling: []("userbio")
I declared 2 variables
var userid;
var useremail;
Then i wrote this future
Future getUserData() async {
var firestore = Firestore.instance;
final FirebaseUser user = await FirebaseAuth.instance.currentUser();
final String uid = user.uid.toString();
final String email = user.email.toString();
DocumentReference qn = await firestore
.collection("users")
.document(email)
.collection('userdetails')
.document(uid);
void initState() {
setState(() {
userid = uid;
useremail = email;
});
}
return qn.documentID;
}
This is how i'm currently trying to setState but it's not working
void initState() {
setState(() {
userid = uid;
useremail = email;
});
}
Please help, thanks.
comment if you need more context
Seems like you're declaring the nested function initState() but you're not calling it. I would just remove the function and call setState() directly :
...
.document(uid);
setState(() {
...
EDIT : Call getUserData() inside initState :
// In a class extending State<T>
#override
void initState() {
super.initState();
// synchronous call if you don't care about the result
getUserData();
// anonymous function if you want the result
() async {
var result = await getUserData();
}();
}
PS : initState(), like build() or setState(), is a predefined name in Flutter. Try not to use those names for functions that are not overriding the original ones.
Try assign your variable an empty value instead of leaving it null.
var userid = '';
var useremail = '';