How to merge two streams and emit them simultaneously using StreamBuilder - flutter

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

Related

Class '_JsonQuerySnapshot' has no instance method '[]' flutter

I want to show "No posts" text when database don't have any posts (length = 0)
(Database's post type is list)
When I use this code, it shows error
Error here :
════════ Exception caught by widgets library ═══════════════════════════════════
Class '_JsonQuerySnapshot' has no instance method '[]'.
Receiver: Instance of '_JsonQuerySnapshot'
Tried calling: []("posts")
My code here:
return StreamBuilder(
stream: FirebaseFirestore.instance
.collection("users")
// .orderBy('datePublished', descending: true)
.snapshots(),
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
if (snapshot.data['posts'].length != 0) {
return showWidget();
} else {
return Container(
child: Center(child: Text("No posts")),
);
}
} else {
return const Center(
child: CircularProgressIndicator(color: Colors.red),
);
}
});
you're trying to get snapshots for a whole collection with :
FirebaseFirestore.instance
.collection("users")
.snapshots(),
this will return a QuerySnapshot which will contain the docs list, so you need to iterate over them to check:
return StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection("users")
// .orderBy('datePublished', descending: true)
.snapshots(),
builder: (context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasData) {
final isCollectionEmpty = snapshot.data!.docs.isEmpty;
final DocumentsWhichContainsPosts = snapshot.data!.docs.where((doc) => (doc.data() as Map<String, dynamic>)["posts"].isNotEmpty);
if (DocumentsWhichContainsPosts.isNotEmpty) {
return showWidget();
} else {
return Container(
child: Center(child: Text("No posts")),
);
}
} else {
return const Center(
child: CircularProgressIndicator(color: Colors.red),
);
}
});

convert this into streambuilder in flutter

I want to convert this function into Streambuilder, but somehow I could not figure out how I could do it. Any help would be greatly appreciated.
Future getReceiverChats() async {
var data = await FirebaseFirestore.instance
.collection("message")
.doc(widget.id)
.collection("nodes")
.orderBy("time", descending: false)
.get();
setState(() {
_msgReceiverList =
List.from(data.docs.map((doc) => Message.fromMap(doc)));
});
}
Try this:
Stream<List<Message>> getReceiverChats(String id) {
return FirebaseFirestore.instance
.collection("message")
.doc(id)
.collection("nodes")
.orderBy("time", descending: false)
.snapshots()
.map((QuerySnapshot query) {
List<Message> dataList = [];
query.docs.forEach((doc) {
dataList
.add(Message.fromMap(doc));
});
return dataList;
});
}
Then:
StreamBuilder<List>(
stream: getReceiverChats(widget.id),
builder: (context, snapshot) {
if (snapshot.hasData) {
final List<Message>? dataList = snapshot.data;
if (dataList!.isEmpty) {
return Center(
child: Text('No results'),
);
}
return ListView.builder(
itemCount: dataList.length,
itemBuilder: (context, index) {
return MyWidget(dataList[index]);
});
}
if (snapshot.connectionState == ConnectionState.done) {
if (!snapshot.hasData) {
return Center(
child: Text('No results'),
);
}
}
return const Center(
child: CircularProgressIndicator(),
);
})
StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection("message")
.doc(widget.id)
.collection("nodes")
.orderBy("time", descending: false)
.snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text("Error: ${snapshot.error}");
}
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Text("Loading...");
default:
return ListView(
children: snapshot.data.docs.map((doc) {
return Message.fromMap(doc);
}).toList(),
);
}
},
),

The argument type 'Future<Stream<QuerySnapshot<Object?>>>' can't be assigned to the parameter type 'Stream<QuerySnapshot<Object?>>?'

I have a function which fetch a stream and do querysnapshot in cloud firestore to fetch some data and then return it.
Future<Stream<QuerySnapshot>> getSearchedUser() async {
final Stream<QuerySnapshot> users = FirebaseFirestore.instance
.collection("users")
.where("email", isEqualTo: searchUserTextController.text)
.snapshots();
return users;
}
and I am trying to function as stream in my stream builder but I am getting an error
StreamBuilder<QuerySnapshot>(
stream: getSearchedUser(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return const Text("Error Occured");
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text("Loading Data");
}
final data = snapshot.requireData;
return ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: data.size,
itemBuilder: (context, index) {
return Text("${data.docs[index]["email"]}");
});
},
),
Error is: -
The argument type 'Future<Stream<QuerySnapshot<Object?>>>' can't be assigned to the parameter type 'Stream<QuerySnapshot<Object?>>?'.
Actually, you don't need 'Future' keyword because Stream already async structure.
So, you can ty below code;
Stream<QuerySnapshot> getSearchedUser() async {
return FirebaseFirestore.instance
.collection("users")
.where("email", isEqualTo: searchUserTextController.text)
.snapshots();
}

Fetching Data from Firestore Collections snapshot.hasdata returns Null

So I have been following a tutorial from YouTube and when I was trying to fetch the "username" from the collection named "users" through Stream snapshot, it returns null.
Aim: Display the available users when search button with username is clicked.
Here is the code below:
bool isSearching = false;
Stream? usersStream;
TextEditingController searchUsernameEditingController =
TextEditingController();
onSearchBtnClick() async {
isSearching = true;
setState(() {});
usersStream = await DatabaseMethods()
.getUserByUsername(searchUsernameEditingController.text);
setState(() {});
}
Widget searchUserList() {
return StreamBuilder(
stream: usersStream,
builder: (context, AsyncSnapshot snapshot) {
return snapshot.hasData
? ListView.builder(
itemCount: snapshot.data.docs.length,
itemBuilder: (context, index) {
DocumentSnapshot ds = snapshot.data.docs[index];
return Image.network(ds["imgUrl"]);
})
: Center(child: Text("No Users Found"));
});
}
From DatabaseMethods():
Future<Stream<QuerySnapshot>> getUserByUsername(String username) async {
return FirebaseFirestore.instance
.collection("users")
.where("username", isEqualTo: username)
.snapshots();
}
Always shows "No Users Found" Message
First confirm that new value for streambuilder is released when onSearchBtnClick is called.
Try this.
onSearchBtnClick() async {
isSearching = true;
setState(() {});
}
Widget searchUserList() {
return StreamBuilder(
stream: DatabaseMethods()
.getUserByUsername(searchUsernameEditingController.text),
builder: (context, AsyncSnapshot snapshot) {
print('called');
// confirm that print is called when onSearchBtnClick is called.
return snapshot.hasData
? ListView.builder(
itemCount: snapshot.data.docs.length,
itemBuilder: (context, index) {
DocumentSnapshot ds = snapshot.data.docs[index];
return Image.network(ds["imgUrl"]);
})
: Center(child: Text("No Users Found"));
},
);
}

Can't combine firestore stream

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