Flutter Firestore - Streambuilder within a streambuilder - firebase

I am trying to calculate the number of unread messages. In the first streambuilder I need to get all the document id's which match the first query.
Within that document ID I can then access the subcollection within that document and perform another query. I then need to access the result of that query.
However, within the attempt below the console outputs "past first stream" but does not enter the second streambuilder.
return StreamBuilder(
stream: Firestore.instance
.collection('conversations')
.where('user_id', isEqualTo: Provider.of<User>(context).id)
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData)
return Center(child: CircularProgressIndicator());
else {
print('past first stream');
StreamBuilder(
stream: Firestore.instance
.collection('conversations')
.document('#32#0#')
.collection('messages')
.where('customer_message_read', isEqualTo: false)
.snapshots(),
builder: (context, snapshot) {
print('im through second stream');
if (!snapshot.hasData)
return Center(child: CircularProgressIndicator());
print('nope');
QuerySnapshot querySnap = snapshot.data;
print(querySnap.documents.length);
return Center(child: CircularProgressIndicator());
},
);
return Scaffold(
backgroundColor: Colors.black,
body: _children[_selectedPage],
bottomNavigationBar: _bottomNavigationBar(context),
resizeToAvoidBottomPadding: true,
);
}
},
);

You've created second StreamBuilder but did not return it

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.

Flutter give Streambuilder initalData

I have a Streambuilder that takes Firebase Firestore snapshot as a Stream and I would like to add initalData to the Streambuilder.
How can I pass initalData to the Streambuilder?
The Code:
StreamBuilder(
stream: FirebaseFirestore.instance
.collection("events")
.snapshots(),
builder: (BuildContext ctx, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData || snapshot.data.docs.isEmpty) {
return NoDataRelatedLocation();
}
if (snapshot.hasError) {
return Text(snapshot.error.toString());
} else {
return new RelatedLocationListing(
relatedLocationList: snapshot.data.docs,
);
}
},
),
You can add initialData to StreamBuilder:
StreamBuilder(
initialData: ..... // <~~~ add it here.
stream: ...
builder: ...
You just need to make sure that your initialData matches that type of data coming from the stream. Since QuerySnapshot is a Firebase specific type, you should map your stream to a data type that you can create and that's known to you.
Here's a pseudo code:
initialData: [MyDataType()],
stream: FirebaseFirestore.instance
.collection("events")
.snapshots().map((snapshot) => MyDataType.fromMap(snapshot.doc));

Correct use of Streams with Flutter-Listview

I am trying to display a realtime chat-screen in flutter with with firebase-firestore (equal to the homescreen of whatsapp).
Working: Creating a list of all the contacts "peers". Have a Look at my Listview:
Container(
child: StreamBuilder(
stream:
//FirebaseFirestore.instance.collection('users').snapshots(),
FirebaseFirestore.instance
.collection('users')
.doc(currentUserId)
.collection('peers')
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(themeColor),
),
);
} else {
return ListView.builder(
padding: EdgeInsets.all(10.0),
itemBuilder: (context, index) =>
buildItem(context, snapshot.data.documents[index]),
itemCount: snapshot.data.documents.length,
);
}
},
),
),
not working: Loading specific data for each tile like last message or name. I cant query this at the time of creating my first list (first query returns peer-ids, second returns userdata of a peer-id). My buildItem method consists of another streambuilder, however, as soon as the first streambuilder makes changes, the app freezes.
Widget buildItem(BuildContext context, DocumentSnapshot document) {
return StreamBuilder<DocumentSnapshot>(
stream: FirebaseFirestore.instance
.collection('users')
.doc(document.data()['peerId'])
.snapshots(),
builder: ...
Is this the proper way to nest streams? Simple Listviews are documented quite well, but i couldn't find a good example on this on google. Any help is appreciated.
Try creating your stream just once in initState and pass it onto this method:
//in initState
peersStream = FirebaseFirestore.instance
.collection('users')
.doc(currentUserId)
.collection('peers')
.snapshots(),
Then use stream: peersStream in the StreamBuilder.
Also, it is recommended to use widget-classes over methods for widgets: https://stackoverflow.com/a/53234826/5066615

got an error in flutter require an identifier while using the streambuilder

I have created the widget in flutter app and its connected with the google firebase but i got an error on the StreamBuilder while getting the data document.('quick')
Widget _createBody() {
return StreamBuilder(
stream: Firestore.instance
.collection('notes').document.('quick').snapshots(),
builder: (context, snapshot){
if(snapshot.hasData){
var doc = snapshot.data;
if (doc.exists){
return Text(doc['content']);
}
}
return CircularProgressIndicator();
}
);
}
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('notes').snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError)
return Center(
child: Text('Error: ${snapshot.error}'),
);
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Center(
child: Text('Loading...'),
);
default:
return new ListView(
children: snapshot.data.documents.map((
DocumentSnapshot document) {
return ListTile(
title: Text(document['content']),
);
}).toList(),
}
}
};
It should be something like this if you're fetching a single document.
Widget _createBody() {
return StreamBuilder(
stream: Firestore.instance
.collection('notes')
.document('quick')
.get()
.snapshots(),
builder: (context, snapshot){
if(snapshot.hasData){
var doc = snapshot.data;
if (doc.exists){
return Text(doc['content']);
}
}
return CircularProgressIndicator();
}
);
}
If this doesn't work, you can always change it like so:
Firestore.instance
.collection('notes')
.document('quick')
.get()
.then((DocumentSnapshot ds) {
// use ds as a snapshot
});

Resources