I'm new to flutter and firebase so bear with me. I'm using email sign up with firestore and flutter on my app, on registration some additional fields are saved to firestore. I want to retrieve those fields to display on the user profile.
The key identifier for the fields saved to the users collection is the auto generated user id upon sign up.
I have in my widget build context
child: new FutureBuilder<FirebaseUser>(
future: _firebaseAuth.currentUser(),
builder: (BuildContext context, AsyncSnapshot<FirebaseUser> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
String userID = snapshot.data.uid;
_userDetails(userID);
return new Text(firstName);
}
else {
return new Text('Loading...');
}
},
),
And my get associated data method is:
Future<void> getData(userID) async {
// return await Firestore.instance.collection('users').document(userID).get();
DocumentSnapshot result = await Firestore.instance.collection('users').document(userID).get();
return result;
}
To retrieve the user details
void _userDetails(userID) async {
final userDetails = getData(userID);
setState(() {
firstName = userDetails.toString();
new Text(firstName);
});
}
I have tried adding a .then() to the set state in _userdetails but its saying userDetails is a type of void and cannot be assigned to string.
The current code block here returns instance of 'Future' instead of the user Details.
Your method is marked as async so you have to await for the result :
Future<void> _userDetails(userID) async {
final userDetails = await getData(userID);
setState(() {
firstName = userDetails.toString();
new Text(firstName);
});
}
Related
I am fairly new to Flutter and following some tutorials I've managed to create a little app. Users can make an account and post announcements about their pets. All these announcement go to the home page, which is built using a Stream. Each announcement has a field that saves the UID of the user who posted it. What I am trying to do now is make the home page display all the announcements, except for the ones the current user posts. I want to do so by comparing the .userId field in the announcements to the UID of the current user.
StreamBuilder(
stream: getUsersPets(context),
builder: (context, snapshot) {
if (!snapshot.hasData)
return Text(
"Loading...",
);
return new ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder:
(BuildContext context, int index) =>
buildPetsList(context,
snapshot.data.documents[index]),
);
})
This is how the buildPetsList function looks like:
Widget buildPetsList(BuildContext context, DocumentSnapshot document) {
final pet = Pet.fromSnapshot(document);
Size size = MediaQuery.of(context).size;
if (pet.userId == 'tLH3ZZvxOEMQkdSL63jncQgbqN32') {
return Container(
height: 0,
);
} else { ...
This works fine with the hardcoded UID, but I want it to be stored in a variable and comparing it that way, obviously.
I have a method inside my Auth class but I don't know how to use it to get what I want.
class AuthService {
final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
Stream<String> get onAuthStateChanged => _firebaseAuth.onAuthStateChanged.map(
(FirebaseUser user) => user?.uid,
);
// GET UID
Future<String> getCurrentUID() async {
return (await _firebaseAuth.currentUser()).uid;
}
// GET CURRENT USER
Future getCurrentUser() async {
return await _firebaseAuth.currentUser();
}
}
So for the beginning I tried to get just the name of the user from Firebase and display it on the home page but it just display the widget's name.
Im using google/facebook/anonymous/email & password login methods.
Firebase instance
Future getCurrentUser() async {
print('done');
return _auth.currentUser;
}
The widget that suppose to get the user details
class GetCurrentUser extends StatefulWidget {
#override
_GetCurrentUserState createState() => _GetCurrentUserState();
}
class _GetCurrentUserState extends State<GetCurrentUser> {
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: Provider.of(context).auth.getCurrentUser(),
builder: (context, snapshot) {
print('done');
if (snapshot.connectionState == ConnectionState.done ) {
return snapshot.data.displayName;
} else {
return Text('error');
}
},
);
}
}
And the String that stores the user's name
String userProfile = GetCurrentUser().toString();
Found a solution!
using the code of this thread's answer How to get the current user id from Firebase in Flutter
I took the void function he made and used it inside the widget I wanted to change the user name too and setState for that string
final FirebaseAuth _ath = FirebaseAuth.instance;
void inputData() {
final User user = _ath.currentUser;
setState(() {
userProfile = user.displayName;
});
}
I am saving data of different users in their uid node and in uid node i have generated different keys in which i have saved data. I my trying to retrieve email,username from keys node.
I have tried to fetch email,username using this code:-
`
#override
void initState() {
super.initState();
getCurrentUser();
rootRef.child('Manager').child(loggedInUser.uid).child(accountKey);
rootRef.once().then((DataSnapshot snap) {
var value= snap.value;
print(value['username']);
}
);
}
`
but i am getting a null value.
How could i retrieve email, username and display it to Text widget.
You need to use a FutureBuilder() since the uid will be null in the above code, therefore create a method that will return a Future<DataSnapshot>:
Future<DataSnapshot> getData() async{
var user = await FirebaseAuth.instance.currentUser();
final dbRef = FirebaseDatabase.instance.reference().child('Manager').child(user.uid).child(accountKey);
return await dbRef.once();
}
Then use it inside FutureBuilder:
FutureBuilder(
future: getData(),
builder: (context, AsyncSnapshot<DataSnapshot> snapshot) {
if (snapshot.hasData) {
I am trying to read a list from the db and use it on a listview.builder() but before I can do that I need to have data. But the thing is that I DO have data. I have tried this with a String and Int and it works perfectly.
I am reading and setting the users' data as a list to the db. You will notice that I do have a list in the home called 'userDoc', well I needed a way to insert the client info as a list so I thought of doing it like this(if there is a better way please share), but the thing is that it does save it the db, but whenever I refresh the app all the content is gone off-screen but still saved in the db, and when I actually enter new data after refresh it deletes all the old data and replaces it with a new list in the db, which is not what I want(it's suppose to add on to the list with the already existing data).
I am trying to create a simple to-do list by the way. In short, I am having difficulties reading and writing a list to the db, and for some reason, it's returning null when I actually have data in the db. Please tell me if you need more precise information. Thank you.
Home
import "package:flutter/material.dart";
import "package:innovative_world/services/auth_service.dart";
import "package:innovative_world/models/list_model.dart";
import 'package:innovative_world/services/database_service.dart';
import "package:innovative_world/shared/decoration.dart";
import 'package:innovative_world/shared/loading.dart';
import 'package:provider/provider.dart';
import "package:innovative_world/models/user_model.dart";
import "package:innovative_world/models/list_model.dart";
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
String _text;
final _formKey = GlobalKey<FormState>();
List<String> userDoc = [];
#override
Widget build(BuildContext context) {
final user = Provider.of<UserId>(context);
return StreamBuilder<UserList>(
stream: DatabaseService(uid: user.uid).userListStream,
builder: (context, snapshot) {
if (snapshot.hasData) {
UserList userList = snapshot.data;
return Scaffold(
// App Bar
body: // I cut off some irrelevant code
RaisedButton( // Submit button
onPressed: () async {
if (_formKey.currentState.validate()) {
userDoc.add(_text);
_formKey.currentState.reset();
await DatabaseService(uid: user.uid)
.setUserData(userDoc);
}
},
ListView.builder(
shrinkWrap: true,
itemCount: userDoc.length, // snapshot.data.list.length will give Error null
itemBuilder: (context, index) {
return Card(
child: ListTile(
title: Text(userDoc[index]),
),
);
},
),
],
),
),
);
} else {
return Loading();
}
});
}
}
Database service
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import "package:innovative_world/models/list_model.dart";
class DatabaseService {
// Collection Reference
CollectionReference listCollection = Firestore.instance.collection("list");
// Get current users id
final String uid;
DatabaseService({this.uid});
// Set data to firestore db
Future setUserData(List<String> list) async {
return await listCollection.document(uid).setData({
"list": list
});
}
// UserList snapshot
UserList _userListFromSnapshot(DocumentSnapshot snapshot) {
return UserList(
uid: uid,
list: snapshot.data["list"]
);
}
// Stream for user's to do list
Stream<UserList> get userListStream {
return listCollection.document(uid).snapshots()
.map(_userListFromSnapshot);
}
}
Auth Service
// Register with email and password
Future registerWithEmailAndPassword(String email, String password) async {
try {
AuthResult result = await _auth.createUserWithEmailAndPassword(email: email, password: password);
FirebaseUser user = result.user;
await DatabaseService(uid: user.uid).setUserData(["Test"]); // Set initial Data as snapshot
return _userFromFirebaseUser(user);
}
catch(e) {
print(e.toString());
return null;
}
}
list model
class UserList {
final List<String> list;
final String uid;
UserList({ this.list, this.uid });
}
As far as I've understood, I think you're having a similar problem as I had. First off try seeing what the error is with a small if condition inside your stream builder:
if (snapshot.hasError){
print(snapshot.error.toString);
}
If you get an error that says
type 'List<dynamic>' is not a subtype of type 'List<String>'
then you have to convert the list to String list. To do that, edit your code in _userListFromSnapshot like this:
snapshot.data["list"].cast<String>().toList()
Even if you don't get that exact error you're at least one step forward towards fixing it.
App Flowchart
I have a question about async function in flutter. I write an that use Firebase authentication. I want to make it such that the app will read the Firebase User ID at the top level of the app(Root Page in this case) at the init state function and then pass the user object to its child widget. Since the function to retrieve the user ID is an async function, I run into problem that the child widget get a null value for user ID even though it should not be null. I have already use future builder in the children widget but it doesn't work. Does anyone know how to do it correctly.
The exact error I am getting is "A build function returned null. The offending widget is: FutureBuilder. Build functions must never return null."
RootPage (Parent)
class _RootPageState extends State {
AuthStatus authStatus = AuthStatus.notSignIn;
String cuerrentUserId;
#override
void initState() {
super.initState();
widget.auth.currentUser().then((userId) {
setState(() {
authStatus = userId == null ? AuthStatus.notSignIn : AuthStatus.signIn;
cuerrentUserId = userId;
});
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new FutureBuilder<FirebaseUser>(
future: FirebaseAuth.instance.currentUser(),
builder: (BuildContext context, AsyncSnapshot<FirebaseUser> snapshot) {
switch(authStatus) {
case AuthStatus.notSignIn:
return new LoginPage(
auth: new Auth(),
onSignedIn: _signedIn,
);
case AuthStatus.signIn:
if (snapshot.connectionState == ConnectionState.done) {
return new HomePage(
auth: widget.auth,
onSignedOut: _signedOut,
userId: snapshot.data.uid,
);
}
else {
}
}
}
),
);
}
HomePage (child)
Future<String> setUserData() async {
currentUser = User(widget.userId);
await currentUser.loadUserData();
_userName = currentUser.name;
_userEmail = currentUser.email;
_userPicURL = currentUser.avatar;
print('current user');
print(currentUser.id);
print(currentUser.email);
return _userName;
}
#override
Widget build(BuildContext context) {
return UserProvider(
user: currentUser,
child: new Container(
child: new FutureBuilder(
future: setUserData(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.data!=null) {
...
You could make your main function async in order to decide during app launch if you should show the login or home page as the first screen.
This could look like the following:
Future<void> main() async {
FirebaseUser currentUser = await FirebaseAuth.instance.currentUser();
bool showHomePage = currentUser != null;
runApp(MyApp(showHomePage));
}
You could use the showHomePage param inside MyApp now to determine which screen should be shown initially. That's it.
Bonus: With this approach you also don't need to show a screen for a friction of a second which may be replaced by another one (e.g. show the home screen --> user is not logged in --> replace with login screen). This could look like a glitch in your app.