The Flutter setState function not updating the list after retrieving from Firebase.
I am trying to develop a Flutter app. I am not getting updating the list in setState() function. The list is successfully retrieving from firebase. I have written the firebase connections in Services.dart file.
But my method _getList() is not getting the value in main.dart file.
main.dart
class DetailsPageState extends State<DetailsPage> {
List<Product> list;
#override
void initState() {
_checkUser(); // for getting user id from firebase auth
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Container(
child:new Text("data");
);
)
}
void _checkUser(){
debugPrint("Entering in _checkUser");
this.firebaseAuth.onAuthStateChanged.listen((firebaseUser)async{
_getList(firebaseUser.uid);
});
}
void _getList(String id)
debugPrint("Entering in _getList");
Services.retrieveItems(firestore, uid).then((onValue){
setState(() {
list=onValue;
debugPrint("items list:"+onValue.length.toString());
debugPrint("items list:"+listCart.length.toString());
});
});
}
}
Services.dart
static Future<List> retrieveItems(Firestore firestore, String userId) async {
List<Product> items = new List<Product>();
try {
firestore.collection("Items").document(userId)
.collection("ItemsMain").snapshots().listen((QuerySnapshot snapshot) {
List docList = snapshot.documents;
items = snapshot.documents.map((documentSnapshot) => Product.fromMap(documentSnapshot.data)).toList();
debugPrint("items:"+items.length.toString());
//return items;
});
} on Exception catch (e) {
print (e.toString());
}
debugPrint("items 2:"+items.length.toString());
return items;
}
Expected results:
Entering in _checkUser
Entering in _getList
items:6
items 2:6
items list:6
items list:6
Actual results:
Entering in _checkUser
Entering in _getList
items list:0
items list:0
items 2:0
items:6
You're returning the items before they are loaded. The simplest way to fix this is to use await in retrieveItems to wait for the data to be loaded from Firestore:
static Future<List> retrieveItems(Firestore firestore, String userId) async {
List<Product> items = new List<Product>();
var snapshot = await firestore.collection("Items").document(userId)
.collection("ItemsMain").getDocuments()
List docList = snapshot.documents;
items = snapshot.documents.map((documentSnapshot) => Product.fromMap(documentSnapshot.data)).toList();
debugPrint("items:"+items.length.toString());
return items;
}
You'll note that I:
Call get() instead of listen(). Since listen() starts actively monitoring the collection, it is impossible to say when it is "done". A get() on the other hand, returns the documents once, and is then done.
Removed the exception handling, just to make the code a bit more readable. But I also recommend only adding exception handlers in functional code like this if you're actually handling the exception. Leave "log and continue" handlers for higher-level code, such as your main method.
Related
i am new to flutter and firebase development, so i really don't know how much will it cost me to keep fetching user data from firebase in every screen that i need them in, so i decided to fetch them once and store them in class MyUser static variables as follows:
in MyApp class:
bool isAuthenticated = false;
Future checkAuthenticity() async {
AuthService.getCurrentUser().then((user) async {
if (user != null) {
String myUid = await AuthService.getCurrentUID();
await MyUserController().getCurrentUserFromFirebase(myUid);
if (mounted)
setState(() {
isAuthenticated = true;
});
} else {
if (mounted)
setState(() {
isAuthenticated = false;
});
}
});
}
#override
Widget build(BuildContext context) {
home: isAuthenticated ? Home(passedSelectedIndex: 0) : Register(),
}
from the above code, this line await MyUserController().getCurrentUserFromFirebase(myUid); is as follows:
getCurrentUserFromFirebase(String uid) async {
await FirestoreService().getCurrentUserData(uid);
}
from the above code, this line await FirestoreService().getCurrentUserData(uid); is as follows:
Future getCurrentUserData(String uid) async {
try {
var userData = await FirebaseFirestore.instance.collection('users').doc(uid).get();
MyUser.fromData(userData.data());
} catch (e) {
if (e is PlatformException) {
return e.message;
}
return e.toString();
}
}
from the above code, this line MyUser.fromData(userData.data()); is a constructor in
MyUser class as follows:
class MyUser {
static String uid;
static String name;
static String username;
static String email;
static String userAvatarUrl;
static String location;
static String phoneNumber;
MyUser.fromData(Map<String, dynamic> data) {
uid = data['id'];
name = data['name'];
username = data['username'];
email = data['email'];
userAvatarUrl = data['userAvatarUrl'];
location = data['location'];
phoneNumber = data['phoneNumber'];
}
}
and to make use of all of the following, in each page that i need to load the current user data in, i use for example:
var userId = MyUser.uid
or to show the current user name i use Text('${MyUser.name}');
when i close the app completely and relaunch it again, it should check for authenticity, and complete executing the rest of the code in main() function.
so my questions are:
1) does this have any performance issues when we release the app?
2) does this will really will prevent unnecessary reads that i can consume in every page i need the data in ?
3) is there any better approach to prevent unnecessary reads from firebase, for example to save the current user data as strings and a profile image locally?
pardon me for prolonging the question, but i wanted to share the code itself.
any help would be much appreciated.
As a short answer,
You can make a class of SharedPreferences to store data as strings in key: value manner.
So anywhere you want you can get an instance of that class and reach it from anywhere in the app.
If you also declare some functions which will decode string to json you will get a ready user class instance in return of your function which will make it easier.
So when you want to save user info to Local Storage(SharedPreferences) you may use a function which will encode your User object to string and save it to SharedPreferences as below..
user.dart' as theUser; for conflict issues
class SharedPrefs {
static SharedPreferences _sharedPrefs;
init() async {
if (_sharedPrefs == null) {
_sharedPrefs = await SharedPreferences.getInstance();
}
}
dynamic get user=> _sharedPrefs.getString('user')!=null?theUser.User.fromString(_sharedPrefs.getString('user')):null;
set user(theUser.User user)=> _sharedPrefs.setString('user', jsonEncode(user));
String get accessToken=> _sharedPrefs.getString('access_token');
set accessToken(String accessToken)=> _sharedPrefs.setString('access_token', accessToken);
void removeString(String entry){
_sharedPrefs.remove(entry);
}
}
final sharedPrefs = SharedPrefs();
And in the app anywhere you can use it directly by typing sharedPrefs.user
Is there a method to find out if a document has values for a specific field? For example, I want to see if a document has a "socialMediaTag" field, which in this case does not exist. How can I do that using Flutter?
You can check it by using a boolean:
bool isLiked = false;
Change your logic to modify your variable and tell Flutter to rerender:
checkIfLikedOrNot() async{
DocumentSnapshot ds = await reference.collection("likes").document(currentUser.uid).get();
this.setState(() {
isLiked = ds.exists;
});
}
Call your function from within initState():
#override
void initState() {
super.initState();
checkIfLikedOrNot();
}
For more info please refer to this link:
Context: I'm trying to query and return a String (imgUrl) from Firebase. I'm always able to print the string inside the query, but the returned value is always null. I'm wondering if my query is wrong and am not sure what best practices are.
Database Outline:
Query Function:
This is the code under our DatabaseService() class, which contains all database queries and updating functions.
String getImageUrl(String _uid) {
String _imgUrl;
Firestore.instance
.document('users/$_uid')
.get()
.then((value) => _imgUrl = value['imgUrl']);
return _imgUrl;
}
Main:
getImageUrl() is called under setImage(). The toast under setImage always returns null and so does the code under it.
String _uid;
// Sets variable '_uid' to the uid of the current user
// Gets called in initstate
Future _getUid() async {
FirebaseUser user = await FirebaseAuth.instance.currentUser();
_uid = user.uid;
}
// Sets the profile photo. If there is no existing profile photo online,
// grab the image on the device. If there is no image online OR on the device,
// Display the default image
void setImage(String url) {
// Get the url that's stored in the db
String _tempUrl = DatabaseService().getImageUrl(_uid); // always ends up being null
Fluttertoast.showToast(msg: "_tempUrl: $_tempUrl");
// Rest of the function
}
#override
void initState() {
super.initState();
_getUid();
}
Please let me know what to do to fix this as it's driving me crazy. Thanks in advance.
Change the method to the following:
Future<String> getImageUrl(String _uid) async {
String _imgUrl;
DocumentSnapshot value =
await Firestore.instance.document('users/$_uid').get();
_imgUrl = value['imgUrl'];
return _imgUrl;
}
use async/await to wait for the future to finish, and then call it like the following:
void setImage(String url) async{
// Get the url that's stored in the db
String _tempUrl = await DatabaseService().getImageUrl(_uid); // always ends up being null
Fluttertoast.showToast(msg: "_tempUrl: $_tempUrl");
// Rest of the function
}
I have an issue with utilising the current user's id (UID). The following code 'works' however there are instances where the _currentUID first outputs a 'null' before outputting the correct value.
class _ContactsScreenState extends State<ContactsScreen> {
String _currentUID;
#override
initState() {
super.initState();
loadCurrentUser();
}
loadCurrentUser() async {
var currentUID = await _getCurrentUID();
setState(() {
this._currentUID = currentUID;
});
}
Future<String> _getCurrentUID() async {
FirebaseUser user = await FirebaseAuth.instance.currentUser();
return user.uid;
}
#override
Widget build(BuildContext context) {
if (_currentUID == null){
print("current UserID = null");
} else {
print("current UserID = $_currentUID");
}
return StreamBuilder(
...
So this is actually working fine, outputs the results as expected, however upon inspection the printed output is as follows:
flutter: current UserID = null // why is it printing null?
flutter: current UserID = abcd1234abcd //correct
What is unusual is that this will only occur when the user visit's the screen for the 2nd time. The first time the screen/page loads it will correctly output 'only' the actual Current User ID. It when the user goes back to the same page it then will print current user twice (as shown above).
This is perfectly normal.
loadCurrentUser is async, so will complete some time after the instance if _ContactsScreenState is created. Only then will _currentUID be assigned.
If the framework calls build before that assignment, then it will be null. It's normal to have build simply return Container or a progress indicator if it's null. Once it it assigned, build will be called again. This time it will not be null and you can build the 'normal' screen.
I have a problem, when I go to the view of the home in my app I have to instantiate a user from firebase, at the time of obtaining the name is null.
I do not know how to bring a user and expect it to load asynchronously because the initial state of the widget does not allow the asynchronous tag.
If I assign it to 'Then' it is also null
Thank you!
//Home Widget State
User currentUser = widget.userController.getCurrentUser(); //ERROR
//CONTROLLER
Future<User> getCurrentUser() async {
User user = await _db.collection('users').document(await getCurrentUserUID()).get().then((snapshot){
return User.fromJson(snapshot.data);
});
print(user.toString());
return user;
}
I'm assuming you're looking for something like this?
Map _userProfile;
#override
void initState() {
super.initState();
FirebaseAuth.instance.onAuthStateChanged.listen((user) {
if(user != null) _getUserProfile(user.uid);
});
}
void getUserProfile(String uid) {
_db.collection('users').document(uid).get().then((snapshot){
setState(() {
_userProfile = snapshot.data;
});
});
}