Combine two stream-queries in flutter - flutter

I want to create a streambuilder to download multiple user-profiles from firebase. But to know which users are needed I have to get the user-ids at first, these are stored in an array. So I created a method to download the array and after this is done the streambuilder loads the user-data for each user-id from the array. Here's the code:
Method to get the array (executed in initState()):
Stream<QuerySnapshot> stream() async* {
job = Job.fromJson(await FirebaseFirestore.instance
.collection("jobs")
.doc(widget.jobId)
.get());
applicants = job.applicants;
await FirebaseFirestore.instance
.collection('users')
.where('uid', whereIn: applicants)
.snapshots();
}
And the streambuilder in the scaffolds' body:
body: isLoading
? Center(child: Container(child: CircularProgressIndicator()))
: applicants.isEmpty
? Center(
child: Text("no values"),
)
: StreamBuilder<QuerySnapshot>(
stream: stream(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
} else { xy }
So my question is if there's a possibility to combine the first method and the stream. Because at the moment the user can't get any update if an application is withdrawn while using the screen.

Related

how to perform query to Firestore and order result Randomly in Flutter

I am retrieving specific documents from firestore collection using flutter stream builder.
the issue is I would like to display the results every single time in a different order (Randomely).
the stream is the below:
stream: FirebaseFirestore.instance
.collection('BusinessProfilesCollection')
.where('Profile_direct_category',
isEqualTo: selecteddirectcategory)
.where('Profile_status', isEqualTo: "Active")
.where('Profile_visibility', isEqualTo: "Yes")
.where('Profile_city',
isEqualTo: globaluserdefaultcity)
.where('Profile_pinning_status',
isEqualTo: "No")
.snapshots(),
the problem is everytime the user do the query the data is returned in the same order. I would like to shuffle it somehow so I remove any advantage from any profile. (document)
I assume you have a list somewhere, where you display your documents? If so, you can use the .shuffle() operator on it! Example:
import 'dart:async';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
String selecteddirectcategory = 'selecteddirectcategory';
String globaluserdefaultcity = 'globaluserdefaultcity';
class RandomResultsScreen extends StatefulWidget {
#override
_RandomResultsScreenState createState() {
return _RandomResultsScreenState();
}
}
class _RandomResultsScreenState extends State<RandomResultsScreen> {
Stream<QuerySnapshot> myStream = FirebaseFirestore.instance
.collection('BusinessProfilesCollection')
.where('Profile_direct_category', isEqualTo: selecteddirectcategory)
.where('Profile_status', isEqualTo: "Active")
.where('Profile_visibility', isEqualTo: "Yes")
.where('Profile_city', isEqualTo: globaluserdefaultcity)
.where('Profile_pinning_status', isEqualTo: "No")
.snapshots();
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder<QuerySnapshot>(
stream: myStream,
builder: (context, asyncSnapshot) {
List<Widget> docs = [];
QuerySnapshot? foundResults = asyncSnapshot.data;
if (foundResults == null) {
//It always wants to be null at first, and then you get errors for calling on null.
return Center(child: CircularProgressIndicator());
} else {
for (QueryDocumentSnapshot doc in foundResults.docs) {
Map<String, dynamic> docData = doc.data() as Map<String, dynamic>;
docs.add(
MyWidget(docData) // Some Widget that you use to display your data
);
}
docs.shuffle(); // <- Where the magic randomization happens!
return ListView.builder(
itemCount: docs.length,
itemBuilder: (context, index) {
return docs[index];
},
);
}
},
),
);
}
}

firestore query inside of streambuilder for getting additional informations in flutter

i have problems with my code. i receive only the data of my first query inside the streambuilder if statement. do i need to use a loop ???
With listenStream i receive for example 4 results. 2 of the results have the value in field 'kategorien' = 'Keine'. These 2 fields i need to complete to show in my programm with the results from the query inside the if statement of the streambuilder. i hope i explained a little to understand my targets.
The problem is, that i receive only the first 'kategorie' for the first entry in the database.
can someone help my please?
Here is my code:
StreamBuilder<QuerySnapshot>(
stream: listenStream,
builder: (context,snapshot){
if (snapshot.hasError) {
return Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Loading");
}
return ListView.builder(
itemCount:snapshot.data?.docs.length,
itemBuilder: (context,index){
String? falscheKat = snapshot.data?.docs[index]['kategorie'].toString();
if(falscheKat=='Keine')
{
FirebaseFirestore.instance.collection('artikel').where('item', isEqualTo: snapshot.data?.docs[index]['item'].toString())
.get().then((value) {
neueKat = value.docs[0].get("item");
});
}
return ListTile(
title: Text(snapshot.data?.docs[index]['item']),
subtitle: Text(neueKat.toString()),
);
},
);
})
and here my Firestore query listenStream:
final Stream<QuerySnapshot> listenStream = FirebaseFirestore.instance
.collection('listen1')
.doc('zuhause')
.collection('artikel').snapshots();
Use another StreamBuilder for the second Firestore query.

How to recover a specific data in the firestore?

I have this structure in the database:
I have a function that return the current user logged
FirebaseAuth auth = FirebaseAuth.instance;
auth.currentUser.uid;
And i want to retrieve "requisicoes" when "idUser" == auth.currentUser.uid;
Basically, the user retrieves the requests created by himself.
That's my StreamBuilder
final _controller = StreamController<QuerySnapshot>.broadcast();
FirebaseFirestore db = FirebaseFirestore.instance;
StreamBuilder<QuerySnapshot> addListenersRequests() {
final stream = db
.collection("requisicoes")
.where("usuario.idUser", isEqualTo: idUsuarioLogado)
.snapshots();
stream.listen((dados) {
_controller.add(dados);
});
return null;
}
Note: idUsuarioLogado is returning correctly currentUser
The problem is that I am getting everything in the "requisicoes" collection, when I create a new user, other users' requests appear on the screen
is there a logic problem in StreamBuilder?
As per you problem ,I think you are fetching the data of your current logged uid .But You want to retrieve the details of particulers users created(i,e documendId) . Then you have to fetch documentId first.Like
body: StreamBuilder(
stream:users.snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
if(!snapshot.hasData){
return Center(
child: CircularProgressIndicator(),
);
}
return ListView(
children: snapshot.data.documents.map((document) {
return Center(
child: Container(
width: MediaQuery.of(context).size.width/2.5,
height: MediaQuery.of(context).size.height/5,
child: Text(document.id),
),
);
}).toList());
}),
You have to fetch document.id. This is your particuler user instead of this.
FirebaseAuth auth = FirebaseAuth.instance;
auth.currentUser.uid;
Note: This will return you current logged userId.
For more details gothrough thislink.
I found the answer here Firestore is ignoring the where clause in the query in flutter
My example is the same as the link above, I have two functions, one addListenersRequests() and the other getCurrentUser(), these two functions I was calling in the initState method, the problem is that getCurrentUser is async, so addListenersRequests () was being executed first, before the value of the variable idUsuarioLogado is filled.
So I merged the two functions into one, so that getCurrentUser can be executed first
Final code:
Future<StreamBuilder<QuerySnapshot>> addListenersRequests() async{
await getDataUser();
final stream = db
.collection("requisicoes")
.where("usuario.idUser", isEqualTo: idUsuarioLogado)
.snapshots();
stream.listen((dados) {
_controller.add(dados);
});
return null;
}
Now it is working correctly.
The strange thing about this story is that the firebase executes the clause and retrieves everything in the collection even when the referenced variable is null

Filter Stream to List

I have the following stream builder:
streamCard() {
return StreamBuilder(
stream: cardsRef
.orderBy("timestamp", descending: true)
.limit(10)
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return circularProgress();
}
List<CustomCard> cards = [];
snapshot.data.documents.forEach((doc) {
cards.add(CustomCard.fromDocument(doc));
});
...
return Stack(
alignment: Alignment.center,
children: cards,
);
I need to filter certain cards from being added to the stream/displayed when:
I'm the owner of the card ('ownerId' == currentUserId)
I've liked the card ('liked.' contains = currentUserId)
OwnerId is a field inside each document and Liked is an array with Id's who have liked it.
I've tried to remove the cards from being added to the cards List<> with .where and .contains, but couldn't properly 'discard' them. I was thinking another option could be to modify the Stack directly, in
children: cards
with cards.removeWhere/.skip, or something like that.
To follow the bloc pattern, business logic should happen in your bloc class. Which mean that you should do all the sorting or filtering in the bloc. When you add a new object into sink, the streambuilder will rebuild.
class BlaBloc {
final BehaviorSubject<List<String>> _results =
BehaviorSubject<List<String>>();
getResults() {
List<String> yourresults = yourapi.getResults();
_results.sink.add(yourresults);
};
getResultsLikedByMe() {
List<String> yourresults = yourapi.getResultsLikedByMe();
_results.sink.add(yourresults);
}
getResultsOwnerIsMe() {
List<String> yourresults = yourapi.getResultsOwnerIsMe();
_results.sink.add(yourresults);
}
BehaviorSubject<List<String>> get results => _results;
}
final blaBloc = BlaBloc();
When you build your streambuilder, point to your Bloc, for example as below:
body: StreamBuilder<List<String>>(
stream: blaBloc.results,
builder: (context, AsyncSnapshot<RecipesResponse> snapshot) {
// build your list here...
}
)
To understand more about Bloc pattern, this is a very useful tutorial you could follow here: RxDart Bloc Pattern

How can you nest StreamBuilders in Flutter?

I have 2 Streams that I need to combine to build a widget, but unlike other questions I have seen I need to nest my streams.
I have a stream that gets a collection of documents from Firestore, and a stream that depends on data from the first to get a subcollection of documents. I would like to combine these into one stream, but they need to be nested since each document has its own subcollection of documents.
Stream 1 (Gets a collection of habits from FireStore):
Stream<List> getHabits(){
final Stream<QuerySnapshot> documents = Firestore.instance
.collection("users")
.document('VtL1sxOoCOdJaOTT87IbMRwBe282')
.collection("habits")
.snapshots();
Stream<List> data = documents.map((doc) {
List data;
final documents = doc.documents;
///Maybe this would work to get history of each doc?
for(int i = 0; i < documents.length; i++){
///not sure what to do
getHistory(documents[i].documentID, DateTime.utc(2019,7,7), DateTime.now());
}
data = documents.map((documentSnapshot) => documentSnapshot).toList();
return data;
});
return data;
}
Stream 2 (Called in Stream 1, Takes DocumentID as a parameter, gets sub-collection of documents):
Stream<List> getHistory(String id, DateTime start, DateTime end) async* {
await for (QuerySnapshot querySnapshot in Firestore.instance
.collection("users")
.document('VtL1sxOoCOdJaOTT87IbMRwBe282')
.collection("habits")
.document(id)
.collection("history")
.where('day', isGreaterThanOrEqualTo: start)
.where('day', isLessThanOrEqualTo: end)
.snapshots()) {
List history;
final documents = querySnapshot.documents;
history = documents.map((documentSnapshot) => documentSnapshot).toList();
yield history;
}
}
Any help on how I can combine these streams in a nested format into one stream to be used with StreamBuilder in flutter would be appreciated!'
EDIT
I am not sure if I am working in the right direction or not but I have tried to implement the solution from spenster and this is what I have at the moment in addition to the functions above.
StreamBuilder<List>(
stream: getHabits(),
initialData: [],
builder: (context, snapshot) {
List<UserHabit> habits = [];
List<Widget> test = List.generate(snapshot.data.length, (index){
List<History> history = [];
DocumentSnapshot doc = snapshot.data[index];
return StreamBuilder(
stream: getHistory(doc.documentID, DateTime.utc(2019,7,7), DateTime.now()),
builder: (context, snapshot) {
if (snapshot.hasError)
return new Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.waiting: return new Text('Loading...');
default:
if(!snapshot.data.isEmpty){ //history collection exists
for(int i = 0; i < snapshot.data.length; i++){
//add to history
history.add(History(
day: snapshot.data[i]['day'].toDate(),
dateCompleted: snapshot.data[i]['dateCompleted'].toDate(),
morning: snapshot.data[i]['morning'],
afternoon: snapshot.data[i]['afternoon'],
evening: snapshot.data[i]['evening'],
anytime: snapshot.data[i]['anytime'],
));
}
}
habits.add(UserHabit(
name: doc['habit'],
color: doc['color'],
icon: doc['icon'],
repeat: doc['repeat'],
daily: doc['daily'],
weekly: doc['weekly'],
monthly: doc['monthly'],
time: doc['time'],
history: history,
));
print(habits); //returns each iteration of assembling the list
return Text("i dont want to return anything");
}
},
);
}
);
print(habits); //returns empty list before anything is added
return Column(
children: test,
);
},
),
The Class for UserHabits and History can be shared, but they are just basic classes that assign types and allow easy access.
I have done something similar simply using nested StreamBuilders. Depending on how you want your Widgets organized, you can create streams within the outer StreamBuilder. Based on your clarifying comments, this is one possibility:
#override
Widget build(BuildContext context) {
var habits = Firestore.instance
.collection("users")
.document('VtL1sxOoCOdJaOTT87IbMRwBe282')
.collection("habits")
.snapshots();
return StreamBuilder<QuerySnapshot>(
stream: habits,
builder: (context, snapshot) {
if (!snapshot.hasData)
return Text("Loading habits...");
return ListView(children: snapshot.data.documents.map((document) {
var query = Firestore.instance
.collection("users")
.document('VtL1sxOoCOdJaOTT87IbMRwBe282')
.collection("habits")
.document(document.documentID)
.collection("history")
.where('day', isGreaterThanOrEqualTo: start)
.where('day', isLessThanOrEqualTo: end)
.snapshots();
return StreamBuilder<QuerySnapshot>(
stream: query,
builder: (context, snapshot) {
if (!snapshot.hasData) return Text("Loading...");
// right here is where you need to put the widget that you
// want to create for the history entries in snapshot.data...
return Container();
},
);
}).toList());
},
);
}
Try merging your streams with something like Observable.zip2(stream1,stream2,zipper) or Observable.combineLatest2(streamA, streamB, combiner).
For more info, check this post