Getting and storing user data after logging/sign up - firebase

I want to load user profile information from firestore after the user logs in and I want that data to be available to the whole app when the data is logged in. I plan on doing the later part using inherited widgets in flutter. I can load the data from firestore correctly and get all the data but the home page does not wait for the data to load before continuing so I get errors. It calls the method, then ignores the await part in the method and proceeds on, so I get errors, then after the errors, I get the data messages that the data has loaded.
I have tried a couple of things. I have all my user management code (login, sign up, ...) in one file and I wrote a getuserProfie() method in that file. The method gets all the data, populates a user model with the data and returns that model. See code below.
getUserProfile() async {
UserModel theUser;
await FirebaseAuth.instance.currentUser().then((user) {
Firestore.instance
.collection('/users')
.where('uid', isEqualTo: user.uid)
.snapshots()
.listen((data) {
print('getting data');
theUser = new UserModel(user.uid, data.documents[0]['email'],);
print('got data');
print(theUser.email);
return theUser;
});
}).catchError((e) {
print("there was an error");
print(e);
});
}
I then call this method in my home page as follows
#override
void initState() {
Usermode user = UserManagement().getUserProfile(); //Calling the method
super.initState();
}
This calls the method just fine and gets all the data but the program does not wait until the data is gotten to proceed. I tried moving the code that gets the data into the initState() method and calling super.initState() when I was sure there was a value but that did not work. I also tried calling the getUserProfile() before calling super.initstate() but that did not work.
I tried adding async to the initState() header but flutter does not like that. I can get the data from firebase fine but making the program wait until the data is gotten is the problem I am having. Seems like using await and async is not working. Any other suggestions to make sure the data is loaded before continuing ? I thought about using FutureBuilder but I am not sure how that would work in this case.

initState() can't be a async function.
What You Can try is - Move Usermode user = await UserManagement().getUserProfile(); to a new async function.
Usermode user;
#override
void initState() {
super.initState();
_getUser();
}
Void _getUser() async {
user = await UserManagement().getUserProfile();
setState(() {});
}
and in Build Method you can Check for User Value then pass the Widgets.
#override
Widget build(BuildContext context) {
if (user != null) {
return ProfilePage();
} else {
return CircularProgressIndicator();
}
Now whenever User Data is Loaded it will call setState() & your Profile Page will Load.
For this you need to make - Usermode user; state variable.

Since getUserProfile is async, you'll need to use await when calling it:
#override
void initState() async {
Usermode user = await UserManagement().getUserProfile();
super.initState();
}

After doing much research, I ended up using Flutter StreamBuilder. It was perfect for what I wanted to use it for. I call the method UserManagement().getUserProfile() and get the snapshot of the data from the returned value. If the data is loading, I display the CircularProgressIndicator(). If the snapshot is null, I display error message. See code snippet below.
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: UserManagement().getClientProfile(curUserID).snapshots(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
UserModel _user = new UserModel.from(snapshot.data)
return Scaffold(
body: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(_usermodel.name),
],
});
}else if (snapshot.connectionState == ConnectionState.waiting) {
return Container(child: Center(child: CircularProgressIndicator()));
} else {
return Container(
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Icon(Icons.warning),
),
Text('There was an error please try again')
],
),
);
}
}
In this example, I am using StreamBuilder but you can also use FutureBuilder. StreamBuilder listens for changes in the data and updates the UI accordingly while FutureBuilder gets the data once until the page is loaded again. I had to make changes to my UserModel and also adjusted the way I stored my data but it all worked out. Got most of my solution from here

Related

How can I read the data in firebase with flutter

I tried some things but I cant read the data. I'm a beginner in flutter and I have to do a project that it shows the data in firebase. Can you show a beginner project that it only shows the datas in direbase?
I tried this too but this code have some errors on await (how it's possible I copied this from flutter official site)
import 'package:firebase_database/firebase_database.dart';
DatabaseReference ref = FirebaseDatabase.instance.ref("users/123");
// Get the data once
DatabaseEvent event = await ref.once();
// Print the data of the snapshot
print(event.snapshot.value);
Thanks for replys I checked the await keyword and correct them (I think)
But I dont know how to print this
Can you show me that too
And I edited code to this =>
Future<Object?> GetData() async {
FirebaseDatabase database = FirebaseDatabase.instance;
DatabaseReference ref = FirebaseDatabase.instance.ref("litre");
// Get the data once
DatabaseEvent event = await ref.once();
// Print the data of the snapshot
print(event.snapshot.value); // { "name": "John" }
return event.snapshot.value;
}
Wherever you're calling the GetData method you need to await it...or if it is a widget which needs to display data after fetching it from firebase you can wrap it with a future builder.
I hope this solves your problem, if it still doesn't, post with the specific error you're getting.
FutureBuilder(
future: GetData(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
} else {
if (snapshot.error != null) {
return Center(
child: Text('An error occured'),
);
} else {
return Text(snapshot.data); // Or whatever widget you want to return or display
}
}
},
),

In my flutter app, the content of the page is loaded only when we touch the screen. Why?

I had used stateful Widget for this page.
First I run the function in initState to store data into variable as List and then I output the data in the form of listview.
Also I had used async and await for the function in the initState.
I am using firebase to load data into by flutter app.
So the content of the page is loaded when I touch the screen, before touching the screen it is just black blank screen. But as soon as I touch the screen, suddenly the data is loaded in the form of ListView. So I want that data should loaded even without first touching the screen.
So any one can explain me that why this is happening, and how to stop this. I want to load data as soon as the data is downloaded and processed in background.
code:-
void initState(){
super.initState();
findFollowers(); }
findFollowers({bool clearCachedData = false}) async
{
if(clearCachedData) {
setState(() {
followersResults = null;
});
}
List<UserResult> searchFollowersResult = [];
QuerySnapshot allFollowers = await followersRefrence.doc(widget.userProfileId).collection("userFollowers").limit(limit).get();
allFollowers.docs.forEach((document) async {
QuerySnapshot ds = await usersReference.where("id", isEqualTo: document.id).get();
ds.docs.forEach((docu) {
User eachUser = User.fromDocument(docu);
UserResult userResult = UserResult(eachUser);
searchFollowersResult.add(userResult);
});
});
setState(() {
followersResults = searchFollowersResult;
if(allFollowers!=null)
{
if(allFollowers.docs.length != 0)
{
startAfter = allFollowers.docs[allFollowers.docs.length-1];
}
}
});
}
return ListView(children: followersResults, controller: scrollController,
physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),);
A good approach to asynchronously reading and displaying data is to use FutureBuilder with ListView.builder, e.g.
Widget build(BuildContext context) {
log.v('build called');
final postService = Provider.of<PostService>(context);
return FutureBuilder(
future: postService.getPosts(),
builder: (ctx, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting)
return Center(child: CircularProgressIndicator());
final List<Post> posts = snapshot.data;
return ListView.builder(
itemCount: posts.length,
itemBuilder: (context, index) {
ProjectModel post = posts.data[index];
return Column(
Here the postService.getPosts() is an async method to read and return a list of posts. The FutureBuilder invokes its builder with the state of the access, we display a progress indicator while it is loading, then display a ListView when it is loaded. Note that ListView.builder is a more efficient way of rendering widgets in a scrollable list.

How to display a Firebase list in REAL TIME?

I have a TODO List function (Alarmas), but I feel I'm not taking advantage of Firebase's Realtime features enough.
The Widget displays the list very well, however when someone puts a new task from another cell phone, I am not being able to show it automatically, but I must call the build again by clicking on the "TODO button" in the BottomNavigationBar.
Is there a way that the new tasks are automatically displayed without doing anything?
I'm using streams to get the list...
#override
Widget build(BuildContext context) {
alarmaBloc.cargarAlarmas();
///---Scaffold and others
return StreamBuilder(
stream: alarmaBloc.alarmasStream,
builder: (BuildContext context, AsyncSnapshot<List<AlarmaModel>> snapshot){
if (snapshot.hasData) {
final tareasList = snapshot.data;
if (tareasList.length == 0) return _imagenInicial(context);
return ListView(
children: [
for (var itemPendiente in tareasList)
_crearItem(context, alarmaBloc, itemPendiente),
//more widgets
],
);
} else if (snapshot.hasError) {
return Text(snapshot.error.toString());
}
return Center (child: Image(image: AssetImage('Preloader.gif'), height: 200.0,));
},
),
And, I read the Firebase Data in this way...
Future<List<AlarmaModel>> cargarAlarmas() async {
final List<AlarmaModel> alarmaList = new List();
Query resp = db.child('alarmas');
resp.onChildAdded.forEach((element) {
final temp = AlarmaModel.fromJson(Map<String,dynamic>.from(element.snapshot.value));
temp.idAlarma = element.snapshot.key;
alarmaList.add(temp); // element.snapshot.value.
});
await resp.once().then((snapshot) {
print("Total list was loaded - ${alarmaList.length}");
}); //I'm using this await to be sure that the full list was loaded, so I can order and process it later
return alarmaList;
}
How can I display a List from Firebase in "true" Real Time?
To properly manage the state of asynchronously loaded data, you should:
Start loading/listening to the data in initState()
Set the data into the state (with setState()) when you receive it or it is updated.
Then render it from the state in the build method.
So in your code that'd be something like:
final List<AlarmaModel> alarmaList = new List(); // this is now a field in the `State` object
#override
void initState() {
super.initState();
Query resp = db.child('alarmas');
resp.onChildAdded.forEach((element) {
final temp = AlarmaModel.fromJson(Map<String,dynamic>.from(element.snapshot.value));
temp.idAlarma = element.snapshot.key;
alarmaList.add(temp);
setState(() {
alarmaList = alarmaList;
})
});
}
#override
Widget build(BuildContext context) {
///---Scaffold and others
return StreamBuilder(
stream: alarmaBloc.alarmasStream,
builder: (BuildContext context, AsyncSnapshot<List<AlarmaModel>> snapshot){
if (snapshot.hasData) {
final tareasList = snapshot.data;
If you only want to repaint once you've gotten a complete update from the database, you can put the call to setState() in a value listener, just use onValue in that instead of once(), as you also want to catch the updates.

Flutter using StreamBuilder with setState updates

I'm using a StreamBuilder inside a build method to retrieve user info from firestore and right after it is displayed with a Listview.builder.
Once i have retrieved the user info, i call another API with the user id's to retrieve some other info i would like to display in the list. This gives me a Future for each user, once the future has been "fulfilled" (inside Future.then()), then i save the data and call SetState(); to rebuild the list with the new data.
The problem is once setState() is called the Build method is rerun which gives me the users again, and this causes an endless loop of retrieving the users and the data from the second API.
This seems like a typical scenario but i don't know how to solve this with StreamBuilder. My previous solution retrieved the users in initState so it was not recalled endlessly.
Any tips?
"getOtherAPIData" loops the users retrieved and adds data "distance" to the list, and the list is sorted based on the user with the most/least distance.
Code:
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder<List<IsFriend>>(
stream: viewModel.isFriendStream(),
builder: (_, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
List<IsFriend> friends = snapshot.data;
if (friends == null || friends.isEmpty) {
return Scaffold(
body: Center(
child: Text("Friend list is empty :("),
)
);
} else {
userList = friends;
getOtherAPIData();
return getListview(userList);
}
}
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
)
);
}
),
..
..
void getOtherAPIData() async {
for (IsFriend friend in userList) {
await fitbitServ.getData(friend.user.uid).then((fitData) {
setState(() {
setDistanceOnUser(fitData.distanceKm, friend.user.uid);
totalDistance += fitData.distanceKm;
sortUsers(userDataList);
userData[friend.user.uid] = fitData;
});
});
}
}
You should never call a method that starts loading data from within your build method. The normal flow I use is:
Start loading the data in the constructor of the widget.
Call setState when the data is available.
Render the data from the state in build.

Initialize a variable with local database value

I want to initialize a variable with a value that I have in my local sql database. This is the code:
class _SettingsState extends State<Settings>{
String morning = 'empty';
#override
void initState() {
super.initState();
initAsync();
}
void initAsync() async{
morning = await DatabaseHelper.instance.queryOrario(1);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Text(morning),
),
);
}
The problem is that when I first arrive on this page, I have this error:
A non-null String must be provided to a Text widget.
referred to the line: child: Text(morning),
Then, when I hot reload the page, the code works fine. Why? What am I doing wrong? There is a way to do what I want to do?
(I'm assuming that morning's default value of 'empty' is something you added to your code before posting it here, and it's actuall default value is null. Otherwise, it would make no sense for you to be getting the error you are getting.)
The problem is that your widget is initializing and building before the future has a chance to finish. By the time you have assigned a value for morning, the widget has already been built. As such, when the widget does build, morning is still null. And Flutter heavily frowns on giving a null value to a Text widget.
Instead of depending on a race condition (where two parts of your code are racing to complete first and you are hoping they will complete in a particular order), use a FutureBuilder so you can safely execute futures and have your widget update itself when they are complete:
class _SettingsState extends State<Settings>{
#override
void initState() {
super.initState();
}
Future<String> initAsync() async {
String result = await DatabaseHelper.instance.queryOrario(1);
return result;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<String>(
future: initAsync(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return CircularProgressIndicator();
}
return Text(snapshot.data);
},
),
);
}
}

Resources