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 :)
Related
I'm currently using stream builder and future builder to call two collections at the same time. I'm having hard time because the stream builder refreshes every time the database changes. Here's my source code:
body: StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection('thread')
.orderBy('published-time', descending: true)
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
} else {
return snapshot.data!.docs.length > 0
? MediaQuery.removePadding(
removeTop: true,
context: context,
child: ListView(
shrinkWrap: true,
children: snapshot.data!.docs.map((DocumentSnapshot postInfo) {
return FutureBuilder<DocumentSnapshot>(
future: userCollection
.doc(postInfo.get('publisher-Id'))
.get(),
My variables are here:
final CollectionReference userCollection =
FirebaseFirestore.instance.collection('users');
final FirebaseAuth _auth = FirebaseAuth.instance;
Also tried calling two streambuilders:
body: StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection('thread')
.orderBy('published-time', descending: true)
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
} else {
return snapshot.data!.docs.length > 0
? MediaQuery.removePadding(
removeTop: true,
context: context,
child: ListView(
shrinkWrap: true,
children: snapshot.data!.docs
.map((DocumentSnapshot postInfo) {
return StreamBuilder<DocumentSnapshot>(
stream: userCollection
.doc(postInfo.get('publisher-Id'))
.snapshots(),
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.done) {
Map<String, dynamic> userInfo =
snapshot.data!.data()
as Map<String, dynamic>;
It doesn't look like there is a better way of calling two collections, but you can achieve less rebuilds by considering some optiomization steps mentioned in this article:
Only wrap the widget that should rebuild during a stream change inside a StreamBuilder
Use the Stream.map to map your stream object into an object that your widget needs to show in UI.
Use the Stream.distinct to create a _DistinctStream in case your widget shouldn’t rebuild when the stream provides the same value in a
row.
Create a separate _DistinctStream for StreamBuilders on initState so that they can save streamed values first if your
streamController streams a new value before the screen's first
build.
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);
},
),
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'm trying to fetch data that belong to the logged in user, however, the "getuserui" is async, for some reason. even though the user is logged in to do stuff inside the app the function still returns a Future....
I've lost count to how many different things i've tried, including .then and such but here's my latest attempt.
#override
Widget build(BuildContext context) {
return SizedBox(
height: 900,
child: StreamBuilder(
stream: () async{
fireFirestore.instance.collection('properties').where('uid', isEqualTo: await _authService.getUserId()) .snapshots(),
},
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (!snapshot.hasData)
return const Text('Loading...');
else {
return ListView.builder( ...............
in case you need to see the getUserId():
Future<String> getUserId() {
return _auth.currentUser().then((value) => value.uid.toString());
}
(i've done this method in both the future way (.then) and the async way (async await)
it just tells me the argument type Future<null> can't be assigned to the parameter type Stream
First, you're passing an async function as a stream, hence your error. Second, you need to wrap your StreamBuilder in a FutureBuilder since it depends on the future _authService.getUserId().
#override
Widget build(BuildContext context) {
return SizedBox(
height: 900,
child: FutureBuilder(
future: _authService.getUserId(),
builder: (context, snapshot) {
if(snapshot.hasData)
return StreamBuilder(
stream: fireFirestore.instance.collection('properties').where('uid', isEqualTo: snapshot.data) .snapshots(),
builder: (context, snapshot) {
...
},
);
return Text('future had no data');
},
),
);
}
I am building a Flutter application and I am having trouble understanding how to implement Firestore. Out of the tutorials I have seen, I only see how to create a snapshot of an entire collection, however in my case, my collection is users, so I only need to snapshot the document of a particular user. There doesn't appear to be documentation on the Firebase docs on how to do this nor is there much documentation on the FlutterFire GitHub page. Please help!
This is the Widget I'm trying to build with StreamBuilder.
#override
Widget build(BuildContext context) {
return new StreamBuilder(
stream: Firestore.instance.collection('users').document(userId).snapshots(),
builder: (context, snapshot) {
return new ListView.builder(
itemCount: //what do I put here?,
itemBuilder: (context, index) => new Item(//And here?),
);
}
);
}
Lets say you want to create a Text with the name parameter from your document
Widget build(BuildContext context) {
String userId = "skdjfkasjdkfja";
return StreamBuilder(
stream: Firestore.instance.collection('users').document(userId).snapshots(),
builder: (context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (!snapshot.hasData) {
return Text("Loading");
}
var userDocument = snapshot.data;
return Text(userDocument["name"]);
}
);
}
This is just one instance. Creating a StreamBuilder on the document will rebuild itself every time the document itself is changed. You can try this code, and then go to your console and change the "name" value. Your app will automatically reflect the changes.
Instead of just one Text, you could build entire tree that uses data from your stream.
If you want to get just at the moment value of the document, you can do so by resolving the Future of get() method on document reference.
var document = await Firestore.instance.collection('users').document(userId).get(),
Each element should be casted to have a reference later in the code.
return new StreamBuilder<DocumentSnapshot>(
stream: Firestore.instance.collection('users').document(userId).snapshots(), //returns a Stream<DocumentSnapshot>
builder: (BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (!snapshot.hasData) {
return new Text("Loading");
}
var userDocument = snapshot.data;
return new Text(userDocument["name"]);
}
);
}
Update 2023 with null safety
class _UserInformationState extends State<UserInformation> {
final _usersStream = FirebaseFirestore.instance
.collection('users')
.doc(FirebaseAuth.instance.currentUser!.uid) // 👈 Your document id change accordingly
.snapshots();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: StreamBuilder<DocumentSnapshot<Map<String, dynamic>>>(
stream: _usersStream,
builder:
(BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.hasError) {
return const Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text("Loading");
}
Map<String, dynamic> data =
snapshot.data!.data()! as Map<String, dynamic>;
return Text(data['fullName']);
},
),
),
);
}
}