Can't combine firestore stream - flutter

So, i want to write query like this
... where from = x or to =x
I can't find any documentation about using where condition. So, i using StreamZip
#override
void initState() {
getEmail();
stream1 = databaseReference
.collection("userChat")
.where("from", isEqualTo: userId)
.orderBy("messageDate", descending: true)
.snapshots();
stream2 = databaseReference
.collection("userChat")
.where('to', isEqualTo: userId)
.orderBy("messageDate", descending: true)
.snapshots();
}
and here is my StreamBuilder
StreamBuilder(
stream: StreamZip([stream1, stream2]),
builder: (context, snapshot) {
print(snapshot.data.documents);
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
return Center(
child: CircularProgressIndicator(),
);
default:
return new Flexible(
child: new ListView.builder(
controller: _scrollController,
padding: new EdgeInsets.all(8.0),
reverse: false,
itemBuilder: (context, index) {
print("Time to show data");
List rev = snapshot
.data.documents.reversed
.toList();
MessageFromCloud messageFromCloud =
MessageFromCloud.fromSnapshot(
rev[index]);
return new ChatMessage(
data: messageFromCloud,
userFullname: userFullname,
userId: userId,
roomId: documentId);
},
itemCount: (messagesCloud != null)
? messagesCloud.length
: 0,
),
);
}
}),
When i run it, i get this error
Class 'List' has no instance getter 'documents'.
Receiver: _List len:2 Tried calling: documents
Did i miss something?

StreamZip - emits lists of collected values from each input stream
It means that your snapshot.data is a List.
Would suggest checking out this answer: Combine streams from Firestore in flutter

Related

ProxyProvider - Struggling using it to return List<int>

i'm new to flutter and i'm trying to use ProxyProvider to return List depending on List
here's the code in main.dart
providers: [
FutureProvider<List<Mixer>>(
create: (_) => mixerdbService.retrieveMixers(),
initialData: <Mixer>[]),
ProxyProvider<List<Mixer>, List<int>>(update: (_, mixers, __) {
final List<int> ints = [];
for (var mixer in mixers) {
shipmentdbService
.retrieveNumberOfShipmentsByMixerId(mixer.id)
.then((value) => ints.add(value));
}
return ints;
}),
and this the method retrieveNumberOfShipmentsByMixerId
Future<int> retrieveNumberOfShipmentsByMixerId(String? mixerId) async {
QuerySnapshot<Map<String, dynamic>> snapshot = await _db
.collection("shipments")
.where("mixer_id", isEqualTo: mixerId)
.get();
return snapshot.docs.length;
}
The provider value is an empty list.
i think that there is a mistake in the logic in update method of the proxyprovider.
if the question is not clear, please ask me for more details
Solution
step#1
FutureProvider<List<Mixer>>(
create: (_) => mixerdbService.retrieveMixers(),
initialData: <Mixer>[]),
ProxyProvider<List<Mixer>, Future<List<int>>>(
update: (_, mixers, __) async {
final List<int> ints = [];
for (var mixer in mixers) {
final value = await shipmentdbService
.retrieveNumberOfShipmentsByMixerId(mixer.id);
ints.add(value);
}
return ints;
}),
step#2: Resolve the Future from the Provider by using FutureBuilder
FutureBuilder<List<int>>(
future: context.read<Future<List<int>>>(),
builder: (_, snapshot) {
if (snapshot.hasData &&
snapshot.data!.isNotEmpty &&
listOfMixers.isNotEmpty) {
return ListView.builder(
itemCount: listOfMixers.length,
itemBuilder: (_, index) {
return MixerCard(
mixer: listOfMixers[index],
numberOfShipments: snapshot.data![index]);
});
}
return Center(
child: CircularProgressIndicator(),
);
},
),
This solution is okay, but i'm still searching for another solution without the use of FutureBuilder.
The problem is that the ProxyProvider is returning a list that's not populated yet because of the async calls with .then.
To solve it return a Future<List<int>> instead and make the update function async. It's going to be like below:
ProxyProvider<List<Mixer>, Future<List<int>>>(
update: (_, mixers, __) async {
final List<int> ints = [];
for (var mixer in mixers) {
final value = await shipmentdbService
.retrieveNumberOfShipmentsByMixerId(mixer.id);
ints.add(value);
}
return ints;
}),
Solution 1
Then add another FutureProvider like so:
FutureProvider<List<int>>(
create: (context) => context.read<Future<List<int>>>(),
initialData: [],
),
This way it's easy to use it like:
final listOfMixers = context.watch<List<Mixer>>();
final listOfInts = context.watch<List<int>>();
return Scaffold(
body: listOfMixers.isNotEmpty && listOfInts.isNotEmpty // <- Here
? ListView.builder(
itemCount: listOfMixers.length,
itemBuilder: (context, index) {
return MixerCard(
mixer: listOfMixers[index],
numberOfShipments: listOfInts[index],
);
})
: const Center(
child: CircularProgressIndicator(),
),
);
Solution 2
Or use a FutureBuilder to get the List<int> out of the Future. Something like the following:
FutureBuilder<List<int>>(
future: context.read<Future<List<int>>>(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return ErrorPage();
}
if (snapshot.data == null) {
return Center(child: CircularProgressIndicator());
}
final listLengths = snapshot.data;
// Do something with `listLengths`
}),

Displaying a Future List Function Firebase GetX

I'm trying to create a user feed, just like that of twitter using Firebase & GetX.
In the code snippet is my function..
List<PostModel> postListFromSnapshot(QuerySnapshot snapshot) {
return snapshot.docs.map((doc) {
return PostModel(
id: doc.id,
text: (doc.data() as dynamic)["text"] ?? "",
creator: (doc.data() as dynamic)["creator"] ?? "",
timestamp: (doc.data() as dynamic)["timestamp"] ?? 0,
);
}).toList();
}
Future<List<PostModel>> getFeed() async {
List<String> usersFollowing = await UserService() //['uid1', 'uid2']
.getUserFollowing(FirebaseAuth.instance.currentUser!.uid);
QuerySnapshot querySnapshot = await FirebaseFirestore.instance.collection("posts").where('creator', whereIn: usersFollowing)
.orderBy('timestamp', descending: true)
.get();
return postListFromSnapshot(querySnapshot);
}
What I want to do is to display the Future function getFeed(), I'm using GetX for state management. So, my problem is how can I display the result of this function using a ListView.Builder()
Here's how I used the Future builder
FutureBuilder(
future: _.listPost,
initialData: [PostModel(id: "2", creator: "Fm", text: "Testing", timestamp: Timestamp.now())],
builder: (BuildContext context, AsyncSnapshot snapshot){
if(snapshot.hasData == null){
return Text("Data is available");
} else{
return ListView.builder(
shrinkWrap: true,
itemCount: snapshot.data.toString().length,
itemBuilder: (context, index){
PostModel posts = snapshot.data[index];
return Column(
children: [
Text(posts.text)
],
);
},
);
}
},
)
And here's the error I got
The following NoSuchMethodError was thrown building:
The method '[]' was called on null.
Receiver: null
Tried calling: [](3)
It also pointed to an error on the
PostModel post line.. the [index] to be precise
First, make your AsyncSnapshot snapshot an AsyncSnapshot<List<PostModel>> snapshot. That is not your primary problem, but it will make things a lot easier to have proper typing and not have to guess around using dynamic.
Your problem is that hasData is a bool. It is either true or false, but never null. I wonder how you got that line past your compiler. Are you using an outdated version of Flutter? You should check this, your compiler is your friend, if it isn't helping you properly, this will be a hard and rocky road.
Anyway, you should check whether there is data, if there is none, you are still waiting:
FutureBuilder(
future: _.listPost,
builder: (BuildContext context, AsyncSnapshot<List<PostModel>> snapshot){
if(!snapshot.hasData){
return CircularProgressIndicator();
} else {
final postList = snapShot.requireData;
return ListView.builder(
shrinkWrap: true,
itemCount: postList .length,
itemBuilder: (context, index){
final post = postList[index];
return Column(
children: [
Text(post.text)
],
);
},
);
}
},
)

How to list favorites/bookmarks in Flutter with Firestore

I have a collection called Stuff that holds a title. Think it like a Twitter post.
{
'stuffID': string
'title': string
'details': string
}
And I have a favorites collection, that hold who favorite the which post. A user can favorite multiple stuff.
{
'userID': string
'stuffID': string
}
From second collection, I want to get all stuffID's that current user favorite. And I want to use those to get rest of the information from first collection. In summary, I want to list all stuff's that user favorite. Like a bookmark list.
I thought I must use two StreamBuilder for achieving this. But I couldn't make it work.
Here is what I manage to do:
#override
Widget build(BuildContext context) {
final FirebaseAuth auth = FirebaseAuth.instance;
final User? user = auth.currentUser;
final userID = user!.uid;
var resultStream = FirebaseFirestore.instance
.collection('favorites')
.where("userID", whereIn: [userID]).snapshots();
return StreamBuilder<QuerySnapshot>(
stream: resultStream,
builder:
(BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot1) {
if (snapshot1.hasError) {
return Text('Something is wrong.');
}
if (snapshot1.connectionState == ConnectionState.waiting) {
return Text("Loading");
}
snapshot1.data!.docs.map((DocumentSnapshot document1) {
Map<String, dynamic> data1 =
document1.data()! as Map<String, dynamic>;
print(data1['stuffID']);
Query _stuffStream = FirebaseFirestore.instance
.collection('Stuffs')
.where('stuffID', isEqualTo: data1['stuffID']);
return StreamBuilder<QuerySnapshot>(
stream: _stuffStream.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot2) {
if (snapshot2.hasError) {
return Text('Something is wrong.');
}
if (snapshot2.connectionState == ConnectionState.waiting) {
return Text("Loading");
}
return ListView(
//showing the data
children:
snapshot2.data!.docs.map((DocumentSnapshot document) {
Map<String, dynamic> data =
document.data()! as Map<String, dynamic>;
String stuffID = data['stuffID'];
return ListTile(
title: Text(data['title']),
subtitle: Text(data['details']),
);
}).toList(),
);
});
});
return const Center(child: CircularProgressIndicator());
});
}
When I use this code, app stucks at loading screen:
I'm trying to work it for two days but all my attempts have failed. Can you please help?
I did more and more research day after day and found the answer. I created the code based on here. Note that I changed the design of my collection in Firebase. But it completely unrelated to rest of code. I just wanted to go with more efficient way to store the data.
In summary, I'm fetching the stuffID from favorites collection. And using that stuffID to get stuffs.
#override
Widget build(BuildContext context) {
final FirebaseAuth auth = FirebaseAuth.instance;
final User? user = auth.currentUser;
final userID = user!.uid;
return StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection('favorites')
.doc(userID)
.collection(userID)
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return const Text("Loadsing...");
return Column(children: [
ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: snapshot.data!.docs.length,
itemBuilder: (context, index) {
//return buildListItem(context, snapshot.data.documents[index]);
return ListView(
scrollDirection: Axis.vertical,
shrinkWrap: true,
children: <Widget>[
StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection('Stuffs')
.where('stuffID',
isEqualTo: snapshot.data!.docs[index]
['stuffID']) //seçilen döküman
.snapshots(),
builder: (context, snap) {
if (!snap.hasData) return const Text("Loading...");
return ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(
snap.data!.docs[index]['stuffImage']),
),
title: Text(snap.data!.docs[index]['title']),
subtitle: Column(
children: <Widget>[
Text(snap.data!.docs[index]['details']),
],
),
);
}),
],
);
}),
]);
},
);
}

how to initState and get data from where firebase

i try to create many account and create blog and every account see blog that post by itself. how to init state where in firebase. I declare variable uid in class and get it from initstate and how to use where in firebase i try to mix it with streambuilder
i declare uid in class and get data by this
void inputData() {
final User? user = auth.currentUser;
setState(() {
uid = user!.uid;
// print('uid =======> $uid');
});
}
my initstate
#override
initState() {
super.initState();
inputData(); }
final Stream<QuerySnapshot> animals = FirebaseFirestore.instance
.collection('animal')
.orderBy('createdate')
// .where('uid', isEqualTo: uid)
.snapshots();
after i use where here .I got error
The instance member 'uid' can't be accessed in an initializer.
Try replacing the reference to the instance member with a different expression
StreamBuilder<QuerySnapshot>(
stream: animals,
builder: (
BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot,
) {
if (snapshot.hasError) {
return Text('Something Went Wrong!');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text('Loading');
}
final data = snapshot.requireData;
return Container(
margin: EdgeInsets.only(top: 65),
child: ListView.builder(
shrinkWrap: true,
itemCount: data.size,
itemBuilder: (context, index) {
});
return ListTile(
title: Text(
'${data.docs[index]['animalName']}',
),
subtitle: Text(
'${data.docs[index]['animalDetail']}',
),
onLongPress: () async {
await processDeleteContent(context, data, index);
},
);
},
),
);
},
),
anyway to use where in streambuilder
Your uid is set in initState() that calls after the class is initialized. So you get uid before it is set. Try to write your animals with get so it runs code written there only when you get animals but not on initialization:
Stream<QuerySnapshot> get animals => FirebaseFirestore.instance
.collection('animal')
.orderBy('createdate')
.where('uid', isEqualTo: uid)
.snapshots();

How to merge two streams and emit them simultaneously using StreamBuilder

I'm trying to make 2 simultaneous queries to Firebase and then stream their results as a single stream, emitting their results one at a time as they come through.
I've tried using this code below, but only one of the stream seems to work, even when both streams should have a result.
Stream<QuerySnapshot> searchQuery(String searchQuery) {
final firstStream = FirebaseFirestore.instance
.collectionGroup("newProduct")
.where('sWords', arrayContains: searchQuery)
.snapshots();
final secondStream = FirebaseFirestore.instance
.collectionGroup("usedProduct")
.where("sWords", arrayContains: searchQuery)
.snapshots();
final mergedStream = Rx.merge([firstStream, secondStream]);
return mergedStream;
}
This is the StreamBuilder where I'm making use of the merged stream
StreamBuilder(
stream: _marketDatabaseService.searchQuery(_searchQuery),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text("An error has occurred!");
} else if (snapshot.hasData &&
snapshot.connectionState == ConnectionState.active) {
List<QueryDocumentSnapshot> querySnapshot =
snapshot.data.documents;
return ListView.builder(
itemCount: querySnapshot.length,
itemBuilder: (BuildContext context, int index) {
return Text(querySnapshot[index]["prN"]);
});
} else {
return Center(
child: CircularProgressIndicator(
backgroundColor: Colors.blue,
),
);
}
},
),
Note: I'm making use of rxdart package in this example.
To query 2 collections in Firebase, use the following code snippet:
Stream<List<QuerySnapshot>> getData(String searchQuery) {
Stream stream1 = FirebaseFirestore.instance
.collectionGroup('newProdut')
.where('sWords', arrayContains: searchQuery)
.snapshots();
Stream stream2 = FirebaseFirestore.instance
.collectionGroup('usedProduct')
.where('sWords', arrayContains: searchQuery)
.snapshots();
return StreamZip([stream1, stream2]);
}
Then in your StreamBuilder Widget you the following code snippet to output the data:
StreamBuilder(
stream: getData(_searchQuery),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text("An error has occurred!");
} else if (snapshot.hasData &&
snapshot.connectionState == ConnectionState.active) {
List<QuerySnapshot> querySnapshot = snapshot.data.toList();
List<QueryDocumentSnapshot> documentSnapshot = [];
querySnapshot.forEach((query) {
documentSnapshot.addAll(query.docs);
});
/// This "mappedData" will contain contents from both streams
List<Map<String, dynamic>> mappedData = [];
for (QueryDocumentSnapshot doc in documentSnapshot) {
mappedData.add(doc.data());
}
return ListView.builder(
itemCount: mappedData.length,
itemBuilder: (context, index) {
return Text(mappedData[index]["prN"]);
});
} else {
return Center(
child: CircularProgressIndicator(
backgroundColor: Colors.blue,
),
);
}
},
),