I have a profile page the shows the user his currently registered email and an edit icon that takes him to the page where he can edit the email.
When the user changes the email successfully, he is returned to the previous page that showed him the registered email.
I want the user to see the new email. But the user keeps seeing the old email until the page is reloaded.
So my first thought was to naturally replace the FutureBuilder with StreamBuilder.
I was not sure how to get a stream of the current user and not the future, so I used the only stream I know for FirebaseUser --> onAuthStateChanged
Widget buildEmail() {
return StreamBuilder(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (BuildContext context, AsyncSnapshot<FirebaseUser> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting)
return Center(
child: SpinKitRing(
color: Colors.white,
size: 30,
));
else if (snapshot.hasError)
return Center(child: Text(snapshot.error));
else {
// final email = snapshot.data.data['email'];
final email = snapshot.data.email;
return Padding(
padding: const EdgeInsets.only(left: 25.0),
child: Text('$email', style: TextStyle(color: Colors.black)),
);
}
},
);
}
This StreamBuilder behaves identically to the FutureBuilder I had before
Widget buildEmail() {
return FutureBuilder(
future: FirebaseAuth.instance.currentUser(),
builder: (BuildContext context, AsyncSnapshot<FirebaseUser> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting)
return Center(
child: SpinKitRing(
color: Colors.white,
size: 30,
));
else if (snapshot.hasError)
return Center(child: Text(snapshot.error));
else {
// final email = snapshot.data.data['email'];
final email = snapshot.data.email;
return Padding(
padding: const EdgeInsets.only(left: 25.0),
child: Text('$email', style: TextStyle(color: Colors.black)),
);
}
},
);
}
}
So I looked into the documentation of onAuthStateChanged and it states:
/// Receive [FirebaseUser] each time the user signIn or signOut
This lead me to check on my update email method and check if my user is being signed in again to the app.
static Future<void> updateEmail(String password, String newEmail, BuildContext context) async {
final user = await CurrentUser.getCurrentUser();
final ref = Firestore.instance.collection('users');
try {
AuthCredential credential = EmailAuthProvider.getCredential(email: user.email, password: password);
AuthResult result = await user.reauthenticateWithCredential(credential); // Sign in?
result.user.updateEmail(newEmail);
ref.document(user.uid).updateData({'email': newEmail});
Navigator.of(context).pop();
Fluttertoast.showToast(msg: 'Success!');
} on PlatformException catch (error) {
print(error);
changeEmailErrorDialog(context, error: error.message);
} catch (error) {
print(error);
changeEmailErrorDialog(context, error: error);
}
}
I think that I am signing in the user since this method prevents the ERROR_REQUIRES_RECENT_LOGIN exception
AuthResult result = await user.reauthenticateWithCredential(credential); // Sign in?
Am I doing something wrong?
How can I get a stream of the user?
The Firebase method for obtaining the current user returns a future
FirebaseAuth.instance.currentUser();
Shouldn't there be a stream version of this method?
There is a stream version of it;
FirebaseAuth.instance.userChanges(),
Example;
StreamBuilder<User>(
stream: FirebaseAuth.instance.userChanges(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
return Text(snapshot.data.displayName);
},
),
Related
I am getting an error
The getter 'uid' was called on null.
Receiver: null
Tried calling: uid
this is the code I am using, the problem is auth.currentUser.uid which is returning null when first called, from what I understand the auth.currentUser is null at first then it return the current user
but I don't get how to handle this situation
the error show on the emulator when between the login screen and home screen after I log in, it appear for a second then it's gone and the home screen appears normally
class HomePage extends StatelessWidget {
final FirebaseAuth auth = FirebaseAuth.instance;
static int count;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
GestureDetector(
child: Icon(Icons.logout),
onTap: () async {
await auth.signOut();
Navigator.of(context).push(MaterialPageRoute(builder: (context) {
return LoginPage();
}));
},
),
],
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
print('add button pressed');
Navigator.of(context).push(MaterialPageRoute(builder: (context) {
return NewNote();
}));
},
),
body: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Notes',
style: TextStyle(
fontSize: 18,
),
),
// Text(count != null ? count.toString() : '0'),
NotesCount(
auth1: auth,
),
Text(auth.currentUser.uid != null ? auth.currentUser.uid : 'No user id'),
SizedBox(
height: 20,
),
NotesGrid(
auth1: auth,
),
],
),
),
),
);
}
}
class NotesCount extends StatelessWidget {
final FirebaseAuth auth1;
NotesCount({this.auth1});
#override
Widget build(BuildContext context) {
CollectionReference notes = FirebaseFirestore.instance.collection('users').doc(auth1.currentUser.uid).collection('notes');
return StreamBuilder<QuerySnapshot>(
stream: notes.snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Loading");
}
return new Text(snapshot.data.docs.length.toString());
},
);
}
}
class NotesGrid extends StatelessWidget {
final FirebaseAuth auth1;
NotesGrid({this.auth1});
#override
Widget build(BuildContext context) {
CollectionReference notes = FirebaseFirestore.instance.collection('users').doc(auth1.currentUser.uid).collection('notes');
return StreamBuilder(
stream: notes.snapshots(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (!snapshot.hasData) {
return Center(child: const Text('Loading events...'));
}
if (snapshot.hasError) {
return Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Loading");
}
return GridView.builder(
shrinkWrap: true,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
itemBuilder: (BuildContext context, int index) {
return NoteCard(
note: Note(
title: snapshot.data.documents[index]['title'],
content: snapshot.data.documents[index]['content'],
datetime: snapshot.data.documents[index]['datetime'].toDate(),
id: snapshot.data.documents[index].documentID,
),
);
},
itemCount: snapshot.data.documents.length,
);
},
);
}
}
It's not a good idea to use currentUser as your primary way to find the signed in user account. It will always be null when the app is first launched. The previously signed in user object doesn't become available until some time later. It's better to follow the instructions in the documentation and set up an auth state listener so you can find out when the user object first becomes available.
FirebaseAuth.instance
.authStateChanges()
.listen((User user) {
if (user == null) {
print('User is currently signed out!');
} else {
print('User is signed in!');
}
});
Use this listener to respond to changes in auth state and trigger an update of your UI accordingly.
I tried adding if else before accessing to current user and the error is gone and the user data shows normally after loading it
Text(auth.currentUser != null ? auth.currentUser.uid : 'Loading...')
i have some code:
getFavSalons(AsyncSnapshot<QuerySnapshot> snapshot) {
return snapshot.data.documents
.map((doc) => SalonBlock(
salonName: doc["salonName"],
location: doc["location"],
workTime: doc["workTime"],
rating: doc["rating"],
))
.toList();
}
and part of code where I building list:
StreamBuilder(
stream: Firestore.instance
.collection("customers")
.document("HAQaVqCPRfM7h6yf2liZlLlzuLu2")
.collection("favSalons")
.snapshots(),
builder:
(context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasData) {
return Container(
margin:
EdgeInsets.only(bottom: screenHeight * 0.33),
child: new ListView(
children: getFavSalons(snapshot),
),
);
}
return LoadingSalon();
}),
and here I use uid:
.document("HAQaVqCPRfM7h6yf2liZlLlzuLu2")
here I have to use currentUser instead of filling myself. How to do this?
The current user in you application can change at any moment. For example:
When the user starts the application, Firebase automatically restores their previous authentication state. But this requires it to call out to the server, so the user is briefly not signed in (currentUser is null) before it is signed in.
While the user is signed in, Firebase refreshes their authentication state every hour to ensure their sign-in is still valid (and for example their account hasn't been disabled). This means that their sign-in state can change even when you don't explicitly call the API.
For these reasons you can't simply call currentUser and expect it to remain valid. Instead you should attach an auth state change listener, which gives you a stream of authentication states.
In your code that builds the UI, you can use this stream of user data inside another stream builder. So you'll have two nested stream builders:
For the user authentication state.
For the database, based on the current user.
So something like (untested for now):
StreamBuilder(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, AsyncSnapshot<User> snapshot) {
if (snapshot.hasData) {
return StreamBuilder(
stream: Firestore.instance
.collection("customers")
.document(snapshot.data.uid)
.collection("favSalons")
.snapshots(),
builder:
(context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasData) {
return Container(
margin:
EdgeInsets.only(bottom: screenHeight * 0.33),
child: new ListView(
children: getFavSalons(snapshot),
),
);
}
return LoadingSalon();
}),
}
return Text("Loading user...");
}),
FirebaseUser is currently deprecated, you can get the CurrentUser like shown below;
FirebaseAuth.instance.currentUser;
If you want to know more about what arguments you can use with it check out their documentation;
https://firebase.flutter.dev/docs/auth/usage
Make sure you have firebase_auth imported to your class
Create instances of FirebaseAuth and User like so:
final auth = FirebaseAuth.instance;
User currentUser;
/// Function to get the currently logged in user
void getCurrentUser() {
currentUser = auth.currentUser;
if(currentUser) {
// User is signed in
} else {
// User is not signed in
}
}
You can call the getCurrentUser function in the initState of a Stateful Class to get the current as the Widget is loaded like so:
#override
void initState() {
getCurrentUser();
super.initState();
}
You can now change your previous code to this:
StreamBuilder(
stream: Firestore.instance
.collection("customers")
.document(currentUser.uid)
.collection("favSalons")
.snapshots(),
builder:
(context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasData && snapshot.connectionState == ConnectionState.active) {
return Container(
margin:
EdgeInsets.only(bottom: screenHeight * 0.33),
child: new ListView(
children: getFavSalons(snapshot),
),
);
}
return LoadingSalon();
}),
This should work for you now :)
Here i am using firebase phone authentication, so whenever i click sigout button it gets signed out from firebase instance but it doesn't redirect to the login page.
Here is the code for the sigout button
return Scaffold(
body: Center(
child: RaisedButton(
child: Text('Signout'),
onPressed: () {
AuthService().signOut();
},
)
)
);
Here is the code for AuthService
class AuthService {
handleAuth() {
return StreamBuilder(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (BuildContext context, snapshot) {
if (snapshot.hasData) {
return HomeScreen();
} else {
return LoginPage();
}
});
}
//Sign out
signOut() {
FirebaseAuth.instance.signOut();
}
//SignIn
signIn(AuthCredential authCreds) {
FirebaseAuth.instance.signInWithCredential(authCreds);
}
signInWithOTP(smsCode, verId) {
AuthCredential authCreds = PhoneAuthProvider.getCredential(
verificationId: verId, smsCode: smsCode);
signIn(authCreds);
}
}
How do i redirect it to the login page when signout button is pressed?
Call your login screen and clear out all the previous paths
you can use below shown code
it will clear all the paths and your history and launch new LoginScreen
Navigator.of(context).pushAndRemoveUntil(
new MaterialPageRoute(
builder: (context) =>
new LoginScreen()),
(route) => false);
I have an app which I want to display documents inside collection.. the collection reference is the uid of the user.
Is there a way to get current user uid and put this uid inside StreamBuilder in stream.
I have tried like so but it did not work and returned null:
class _MyAdsState extends State<MyAds> {
final FirebaseAuth _auth = FirebaseAuth.instance;
Future getCurrentUser() async {
final FirebaseUser user = await _auth.currentUser();
final uid = user.uid;
print(uid);
return uid.toString();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
Expanded(
child: StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection("${getCurrentUser()}").snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> querySnapShot){
if(querySnapShot.hasError){
return Text('Some Error');
}
if(querySnapShot.connectionState == ConnectionState.waiting){
return CircularProgressIndicator();
}else{
final list = querySnapShot.data.documents;
return ListView.builder(
itemBuilder: (context, index){
return ListTile(
title: Text(list[index]["subject"]),
subtitle: Text(list[index]["category"]),
);
},
itemCount: list.length,
);
}
},
)
Getting the UID is an asynchronous operation, so requires a FutureBuilder.
If you want to use the UID to then build a stream, you'll need to have a FutureBuilder for the UID, and then inside of that a StreamBuilder for the stream from the database.
body: FutureBuilder(
future: FirebaseAuth.instance.currentUser(),
builder: (context, AsyncSnapshot<FirebaseUser> snapshot) {
if (snapshot.hasData) {
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection(snapshot.data.uid).snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> querySnapShot){
...
},
)
}
else {
return Text('Loading user data...');
}
THANK YOU GUYS!
I was looking for this for too long now. I had the "problem" that I was recording the senderUID for a sent message only, but of course wanted the Name being displayed in the "sentFrom" field. So I had to query Firestore for the UID and pull out the email. My solution:
FutureBuilder<QuerySnapshot>(
future: _firestore.collection("users").get(),
builder: (context, futureSnapshot) {
if (!futureSnapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
Map<String, String> users = {};
final userData = futureSnapshot.data.docs;
for (var user in userData) {
users[user.id] = user.data()["email"];
}
return StreamBuilder<QuerySnapshot>(
stream: _firestore.collection("messages").snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
// ignore: missing_return
final messages = snapshot.data.docs;
List<Widget> messageWidgets = [];
for (var message in messages) {
final messageText = message.data()["text"];
final messageEmail = users[message.data()["senderUID"]];
messageWidgets
.add(Text("$messageText from $messageEmail"));
}
return Column(children: messageWidgets);
},
);
},
),
I just created a map from the data and used it inside the stream builder. Is there maybe a better solution?
I want to fetch currently logged in user data. There is a field in fire_store 'useremail'. When a user logs in, I get his ID and using 'where class' I fetch the animal's data against his ID shown below:
Widget _buildBody(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('booking_tbl').where("useremail", isEqualTo: _firebaseUser.email.toString()).snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return LinearProgressIndicator();
return _buildList(context, snapshot.data.documents);
},
);
}
Although it is working but it shows error on as well. I need some help to fix this issue or an alternate suggestion for this (Any suggestion or kind help would be highly appreciated):
════════ Exception caught by widgets library
══════════════════════
NoSuchMethodError was thrown building UserBookingHistoryModel(dirty,
state: _UserBookingHistoryModelState#2d8c2):
The getter 'email' was called on null.
Receiver: null
Tried calling: email
Probably the problem is caused by this snippet in Firebase Auth:
void initState() {
super.initState();
widget.auth.getCurrentUser().then((firebaseUserId) {
setState(() {
authStatus = firebaseUserId == null
? AuthStatus.notSignedIn
: AuthStatus.signedIn;
});
});
}
The full code of bookings.dart is here:
class _UserBookingHistoryModelState extends State<UserBookingHistoryModel> {
FirebaseAuth _auth;
FirebaseUser _firebaseUser;
#override
void initState() {
super.initState();
_auth = FirebaseAuth.instance;
_getCurrentUser();
}
_getCurrentUser () async {
_firebaseUser = await FirebaseAuth.instance.currentUser();
setState(() {
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: _buildBody(context),
);
}
Widget _buildBody(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('booking_tbl').where("useremail", isEqualTo: _firebaseUser.email.toString()).snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return LinearProgressIndicator();
return _buildList(context, snapshot.data.documents);
},
);
}
Widget _buildList(BuildContext context, List<DocumentSnapshot> snapshot) {
return ListView(
padding: const EdgeInsets.only(top: 5.0),
children: snapshot.map((data) => _buildListItem(context, data)).toList(),
);
}
Widget _buildListItem(BuildContext context, DocumentSnapshot data) {
final record = Record.fromSnapshot(data);
return Padding(
key: ValueKey(record.animal),
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
),
child: new ListTile(
title: new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new Text(
"${record.animal} Slaughtering",
style: new TextStyle(fontWeight: FontWeight.bold, color: Colors.black),
),
],
),
)
),
);
}
}
class Record {
final String animal;
final String user;
final DocumentReference reference;
Record.fromMap(Map<String, dynamic> map, {this.reference})
: assert(map['animal'] != null),
assert(map['user'] != null),
animal = map['animal'],
user = map['user'];
Record.fromSnapshot(DocumentSnapshot snapshot)
: this.fromMap(snapshot.data, reference: snapshot.reference);
#override
String toString() => "Record<$animal:$user>]";
}
You need to do the following:
Stream<QuerySnapshot> getData() async*{
FirebaseUser firebaseUser = await FirebaseAuth.instance.currentUser();
yield* Firestore.instance.collection('booking_tbl').where("useremail", isEqualTo: firebaseUser.email.toString()).snapshots();
}
Then inside the StreamBuilder use getData():
return StreamBuilder<QuerySnapshot>(
stream: getData(),
builder: (context, snapshot) {
//....
The getData() method is asynchronous, since you are using a StreamBuilder then you need to return a Stream therefore you use the async* keyword, and you emit the result using yield*