Filter Stream to List - flutter

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

Related

Using stream inside stream in firestore using bloc

I have multiple lists from firestore that I get using a stream method and bloc with a subscription, this is working fine. The problem is when I want to add products inside those list. I want that each list has their own products, and I want to get those products with a stream. I tried using a streamBuilder inside those lists to get the products and it worked fine, but I don't think this is a good approach of the bloc pattern (maybe I am wrong and this is a good approach, correct me if I am mistaken please). So I want to use a bloc with a subscription to get the products from each list, the problem is that all the list display the same products that the last list. List example
In this example I can only work with list 3, if I add or delete a product from that list, then that products is showed in the rest of the list, BUT is not created in the collection in firestore, in the collection it creates and deletes properly. My question is why it only displays the last list, is a problem with the bloc stream? Or maybe I am not retrieving the data properly from firestore?
This is the bloc:
on<ProductSubscriptionRequestedEvent>((event, emit) {
String shoppingCartListId = event.shoppingCartListId;
String shoppingListId = event.shoppingListId;
_productsInShoppingListSubscription?.cancel();
_productsInShoppingListSubscription = provider.getProductsFromShoppingList(
shoppingCartListId: shoppingCartListId,
shoppingListId: shoppingListId
)
.listen((products) => add(UpdateProductStreamEvent(products)) );
});
on<UpdateProductStreamEvent>((event, emit){
emit(ProductsStreamState(productList: event.products));
});
This is the way I get the data from firestore:
#override
Stream<Iterable<CloudProducts>> getProductsFromShoppingList({required String
shoppingCartListId, required String shoppingListId}) {
final allProductsShoppingList =
FirebaseFirestore.instance.collection(shoppingListCollectionName)
.doc(shoppingCartListId).collection(shoppingListCartCollectionName)
.doc(shoppingListId).collection(productCollectionName).snapshots().map((event) =>
event.docs
.map((doc) => CloudProducts.fromSnapshot(doc)));
return allProductsShoppingList;
}
And this is how I display the products inside the lists:
return BlocBuilder<ProductBloc, ProductState>(
builder: (context, state) {
if (state is ProductsStreamState) {
final products = state.productList;
return Column(
children: [ListView.builder(
shrinkWrap: true,
physics: const ClampingScrollPhysics(),
itemCount: products.length,
itemBuilder:
(BuildContext context, int index) {
final productName =
products.elementAt(index).name;
final productId = products.elementAt(index).productId;
if (productName.isNotEmpty) {
return Slidable(...);
} else {
return SizedBox.shrink();
}
},
),
Thank you in advance.

Combine two stream-queries in 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.

Is there a way to return a certain value before it goes out of scope for dart/flutter?

Thank you for taking a look at my question. Here is what I have so far.
List test() {
// ignore: deprecated_member_use
List<String> newList = [];
FirebaseFirestore.instance
.collection('userInput/xCj26wS73dslrIXsggrY/userInput2')
.snapshots()
.listen((event) {
event.docs.forEach((docs) {
newList.add(docs['Weight'].toString());
print('1\n');
print(newList);//first prints out '0' then '40'
});
print('2\n');
print(newList); //prints out '0 40' this is what i want the value to be
/**
*
* Is there a way to return here?
* If i try to 'return newList', i get an error of 'The return type 'List<String>' isn't a 'void',
* as required by the closure's context.'
*/
});
print('3\n');
print(newList);//prints out empty list
return newList;//returns empty list
}
I have tried adding comments to make this a little easier to read/understand. I'm trying to access a certain data in a collection and add that value to a list. The issue I am having is by the time i can use a return statement, the variable loses the value I want it to have. Is there a way to call return under where I have ' print('2\n');'? If not, how can i return my list before it goes out of scope? While looking at other recourses, I came across this, which would print out each of the specific data in the collection; however, I want to add each of the data into a new list.
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder(
stream: FirebaseFirestore.instance
.collection('userInput/xCj26wS73dslrIXsggrY/userInput2')
.snapshots(),
builder: (context, AsyncSnapshot<QuerySnapshot> streamSnapshot) {
return ListView.builder(
itemCount: streamSnapshot.data?.docs.length,
itemBuilder: (ctx, index) => Text(streamSnapshot.data?.docs[index]
['Exercise']),
);
},
),
);
}
I would appreciate any tips, feedback, etc.. Thank you.
return within your listen callback makes no sense; where would the returned value go?
Your test function returns an empty List because it returns immediately without waiting to receive all of the asynchronous events from the stream. Streams are asynchronous; your test function must be asynchronous too. One way to wait:
Future<List<String>> test() async {
var newList = <String>[];
await for (var event in FirebaseFirestore.instance
.collection('userInput/xCj26wS73dslrIXsggrY/userInput2')
.snapshots()) {
for (var docs in event.docs) {
newList.add(docs['Weight'].toString());
}
}
return newList;
}
Or, using collection-for:
Future<List<String>> test() async {
return [
await for (var event in FirebaseFirestore.instance
.collection('userInput/xCj26wS73dslrIXsggrY/userInput2')
.snapshots())
for (var docs in event.docs) docs['Weight'].toString(),
];
}

Getting collection data from flirestore and appending into a list so that i can use ListView.builder to build the content in Flutter

Im trying to retrieve data from firestore and putting them into a list so that i can build read and build widgets from the data retrieved. I cant seem do both, i can either get the data, or append a list with a fixed value, but i CANT seem to RETRIEVE DATA + APPEND THE LIST WITH THE RETRIEVED DATA
. Sorry if im not being clear enough, do let me know what do you need, below is my screenshot from my database structure and code snippets.
Database structure :
Data retrieval code snippet :
onRefreshPage() {
Firestore.instance
.collection("testimonies")
.getDocuments()
.then((querySnapshot) {
querySnapshot.documents.forEach((result) {
print(result.data);
});
});
}
List declaration :
List<DocumentSnapshot> testimonyCards = [];
If I understand you correctly you want to transform the data into widgets. Have a look at FutureBuilder from Flutter: https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html and https://firebase.flutter.dev/docs/firestore/usage#realtime-changes
In your case you can do something like:
FutureBuilder<QuerySnapshot>(
future: FirebaseFirestore.instance.collection('testimonies').get(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Loading");
}
return new ListView(
children: snapshot.data.docs.map((DocumentSnapshot document) {
return new ListTile(
title: new Text(document.data()['DisplayName']),
subtitle: new Text(document.data()['TestimonyData']),
);
}).toList(),
);
},
);
If I am correct, you want to query Firebase and append the results to a list. In this case, it is an array. Is this what you are looking for?
Appending data from Firebase to array

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