How to query data from Firebase with an async call to uid - firebase

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');
},
),
);
}

Related

Is there a way to other way of calling two collection in 1 stream builder?

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.

Getting the total price of products in Firestore Flutter

This is my first time asking a question because I've tried everything that I've researched from this website for hours but nothing seems to work. I'm trying to get the total price of my cart but every time the state changes it constantly adds and I just want the total to stay as it is when I'm changing pages. Even clicking a delete button from my cart page, refreshes the function and it adds another value to the total. Thank you so much if someone could help me
Here's the code that I've tried
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: white,
body: StreamBuilder(
stream: FirebaseFirestore.instance
.collection("users")
.doc(user.uid)
.collection("cart")
.snapshots(),
builder:
(BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
FirebaseFirestore.instance
.collection("users")
.doc(user.uid)
.collection("cart")
.get()
.then((querySnapshot) {
querySnapshot.docs.forEach((result) {
total += result.data()['price'];
});
});
First of all why do you send a request inside builder again? There is already a variable called "snapshot". You can get data with snapshot.data.docs.
Secondly, you are trying to increase total value every time without reset. If you set total variable to 0 before adding the prices probably it will solve your problem.
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: white,
body: StreamBuilder(
stream: FirebaseFirestore.instance
.collection("users")
.doc(user.uid)
.collection("cart")
.snapshots(),
builder:(BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if(snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
} else {
total = 0;
snapshot.data.docs.forEach((result) {
total += result.data()['price'];
});
return WidgetYouWantToUse();
}
}
Welcome!
First of all, you are using a StreamBuilder and even though you provide a Stream, you call the stream again at builder method, you can can access the value of the Stream through the snapshot. Moreover, your builder returns no Widget but just iterates through the items of the Stream, to calculate I guess the total price. Instead, you can change the builder method to something like this
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: white,
body: StreamBuilder(
stream: FirebaseFirestore.instance
.collection("users")
.doc(user.uid)
.collection("cart")
.snapshots(),
builder:
(BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
var data = snapshot.data;
var totalCartPrice = data.fold(0,(prev, element)=>prev['price']+element['price']);
return Text(totalCartPrice);
});
Instead of using fold method you can iterate the data and do the calculations by hand. But I find it more elegant.
There was no reason in calling setState aswell, the StreamBuilder fires changes if anything changes in the Firestore document.
The problem is that total value keeps on increasing whenever stream builder is called. Also, you do not need to listen to stream and still call get(). The stream returns similar value to the get.
Change your code to this
return Scaffold(
body: StreamBuilder(
stream: FirebaseFirestore.instance
.collection("users")
.doc(user.uid)
.collection("cart")
.snapshots(),
builder: (BuildContext context, snapshot) {
if (snapshot.hasError) return Text('Something went wrong');
if (snapshot.connectionState == ConnectionState.waiting)
return CircularProgressIndicator();
// NB: I set the value of total = 0; so that anytime the stream
// builder is called, total starts from 0.
total = 0;
snapshot.data.docs.forEach((result) {
total += result.data()['price'];
});
print(total);
print('done');
return Text('done');
},
),
);
On the other hand, you can still call this function using futureBuilder.
return Scaffold(
body: FutureBuilder(
future: FirebaseFirestore.instance
.collection("users")
.doc(user.uid)
.collection("cart")
.get(),
builder: (BuildContext context, snapshot) {
if (snapshot.hasError) return Text('Something went wrong');
if (snapshot.connectionState == ConnectionState.waiting)
return CircularProgressIndicator();
total = 0;
snapshot.data.docs.forEach((result) {
total += result.data()['price'];
});
print(total);
print('done');
return Text('done');
},
),
);
The difference between stream and future builder is that future builder is only called once. Stream builder is called whenever the data changes.

How to use .currentUser method in flutter

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 :)

Flutter: Firestore Get User uid inside StreamBuilder

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?

Using Stream Building with a specific Firestore document

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']);
},
),
),
);
}
}

Resources