I'm writing a flutter app and and using the bloc library. I have a bloc and a cubit, within the state of each is a list of ids of some other documents I need to fetch from firestore. There can be some overlap and some docs are already fetched so I want to get the list of ids from both states, compare them, and then only go to firestore for ones that exist in one but no the other.
I set a new cubit for this:
class CircleRecipesCubit extends Cubit<CircleRecipesState> {
CircleRecipesCubit({
#required RecipesBloc recipesBloc,
#required CirclesCubit circlesCubit,
}) : assert(
recipesBloc != null,
circlesCubit != null,
),
_recipesBloc = recipesBloc,
_circlesCubit = circlesCubit,
super(CircleRecipesInitial());
final RecipesBloc _recipesBloc;
final CirclesCubit _circlesCubit;
StreamSubscription _recipesSubscription;
StreamSubscription _circlesSubscription;
Future<void> getCircleRecipes() async {
// get a list of recipes the user already has loaded
List<String> userRecipesIds;
_recipesSubscription = _recipesBloc.stream.listen((RecipesState event) {
if (event is RecipesLoaded) {
userRecipesIds = event.recipes.map((e) => e.id).toList();
print('*');
print(userRecipesIds);
print('*');
}
});
// get a list of recipes in the circles
List<String> circleRecipeIds;
_circlesSubscription = _circlesCubit.stream.listen((CirclesState event) {
if (event is CirclesLoaded) {
circleRecipeIds = event.circles.fold([],
(previousValue, element) => [...previousValue, ...element.recipes]);
print('|');
print(circleRecipeIds);
print('|');
// List<String> circleOnlyRecipeIds = circleRecipeIds;
// circleRecipeIds.removeWhere((e) => userRecipesIds.contains(e));
// print(circleOnlyRecipeIds);
}
});
// reduce the list of recipes to a set of only circle recipes
//TODO
//------- Try with RX dart
Stream<RecipesState> recipesStream = _recipesBloc.stream;
Stream<CirclesState> circlesStream = _circlesCubit.stream;
Rx.combineLatest2(recipesStream, circlesStream, (
RecipesState recipesState,
CirclesState circlesState,
) {
print("This doesn't print!");
print(recipesState);
print(circlesState);
if (recipesState is RecipesLoaded) {
userRecipesIds = recipesState.recipes.map((e) => e.id).toList();
print('*');
print(userRecipesIds);
print('*');
}
if (circlesState is CirclesLoaded) {
circleRecipeIds = circlesState.circles.fold([],
(previousValue, element) => [...previousValue, ...element.recipes]);
print('|');
print(circleRecipeIds);
print('|');
// List<String> circleOnlyRecipeIds = circleRecipeIds;
// circleRecipeIds.removeWhere((e) => userRecipesIds.contains(e));
// print(circleOnlyRecipeIds);
}
// fetch the set of recipes
});
}
#override
Future<void> close() {
_recipesSubscription.cancel();
_circlesSubscription.cancel();
return super.close();
}
}
So above is my cubit - it listens to the recipesBloc and the circlesCubit. The first two expressions in the getCiricleRecipes() function are only there to prove that its hooked up correctly - when it runs those print statement print the ids I want it to from both the other bloc and the other cubit.
I need the latest values from both though at the same time to compare them - so I thought rx.combinelatest2 would be good. I give it the stream from the bloc and the cubit. But the combiner function doesn't even run even though things seem 'wired up' correctly.
Any help greatly appreciated.
Make sure both streams have already emitted at least one item.
combineLatest documentation states:
The Stream will not emit until all streams have emitted at least one item.
Since the first block (where you subscribe to _circlesCubit) prints, then most likely _recipesBloc is the culprit here.
Related
I'm making a reactive model with Getx on a product list, but when I start the list it comes with no value and causes an index error even though there are actually values in the list it appears empty at first, which somehow gets fixed automatically. (this is inside a build of a statelesswidget)
return GetX<CartController>(
init: CartController(),
builder: (controller) {
try {
return Text(
"${StringConvert.toMoney(controller.totalByProduct[productId])}",
style: kSmallTextBold,
);
} catch (e) {
return const Text("Error...");
}
},
);
}
I did try catch to manage this, but the catch part doesn't show up;
this is relevant part of the controller
var totalByProduct = [].obs;
fetchTotal() {
List products = storage.read(StorageKeys.cartProducts);
double currentValue = 0.0;
List currentTotals = [];
for (var item in products) {
currentTotals.add(item['total'] * item['amount']);
currentValue += item['total'] * item['amount'];
}
total.value = currentValue;
totalByProduct.value = currentTotals;
}
I believe it's not the right way to do this, so what do I need to know to fix this correctly?
If helps this is the error:
With a method to read the storage (sharedPreferences) in async mode, with a FutureBuilder it was possible to correct the error, because in the initial state the list takes the value assigned explicitly. Even if it then receives the correct value, accessing the index in its initial state causes the error, this explains why even with the error it works.
I have a future builder, and Im using two future variables:
Future<List<Notifications>>? notificationsOfTheDay; //get saved notifications from db
Future<List<NotificationCategory>>? notificationsByCat; // sort/modify "notificationsOfTheDay"
I'm sending notificationsByCat to Future Builder.
The issues:
While the app starts, the future builder is able to receive all the notifications and manipulate the data with some asynchronous operations.
But sometimes the Future Builder displays blank.
I'm also appending received notifications to the existing Future Variable notificationsOfTheDay, when sometimes the view does not update.
Code snippets are listed below:
Here is my initState
void initState() {
super.initState();
initPlatformState(); // Notification Listener Service
notificationsOfTheDay = initializeData(isToday);
}
initilizeData Method
Future<List<Notifications>> initializeData(bool istoday) async {
notificationsOfTheDay = initializeNotifications(istoday);
if (notifications!.length > 0) {
notificationsByCat = notificationsByCategory(notificationsOfTheDay); //sorting/manipulation of existing future
}
return notifications!;
}
notificationsByCategory
Future<List<NotificationCategory>> notificationsByCategory(
List<Notifications> notificationsFuture) async {
return await NotificationsHelper.getCategoryListFuture(
isToday ? 0 : 1, notificationsFuture);
}
When any new notifications are received, it is inserted into the db and the exising future is appended with the new notification;
setState(() {
notificationsOfTheDay =
appendElements(notificationsOfTheDay!, _currentNotification!);
notificationsByCat = notificationsByCategory(notifications!);
});
Future<List<Notifications>> appendElements(
Future<List<Notifications>> listFuture,
Notifications elementToAdd) async {
final list = await listFuture;
list.add(elementToAdd);
return list;
}
Can anyone please guide me to a solution? Tried many combinations. If I'm directly showing the data without modifying it according to category, it works fine.
Where am I going wrong?
I am new to flutter and I wonder how to develop filters in flutter something like this (screenshot taken from google images), so I just want to know how to do filtering in a flutter, is there anything like plugins or special widgets? If you provide any reference or code or any tutorials will be helpful for me to learn.ThankYou in Advance.
You need to break it down in few pieces.
First is your UI: these are just standard Flutter Widgets. You want a user to slide it up? Figure out how to show a Widget by sliding up. You want it to be in the alert popup? Figure out how to do alert popup. Filter UI is not different from any other UI - so you can look for and ask generic UI questions.
Second is how you implement the model. It can be something simple like a Provider that holds the list of Items that you fetched; and then each filter adding more where conditions to your list.
Something like:
var items=<Item>[]; // somehow you would fetch the initial list of items
var filtered;
void addColorFilter(Color color) {
filtered=filtered??items;
filtered=filtered.where( (element) => element.color==color);
notifyListeners();
}
void addSizeFilter(String size) {
filtered=filtered??items;
filtered=filtered.where( (element) => element.size==size);
notifyListeners();
}
void removeFilters() => filtered=null;
void getFiltered() => filtered??items;
And then you can use filtered iterator in your ListView.builder() to show only filtered items.
To answer your follow-up question here:
You have mix of 'AND' & 'OR' conditions. If you just keep adding iterators like the above, you won't be able to show 2 sizes (M and S) - because no items is both M and S. In this case, where there is a multiple choice filter, you will need to add additional list for each filter type that can have multiple choice. And you will have to rebuild your entire filter.
This might be a good starting point - for your price and size example:
var items=<Item>[]; // somehow you would fetch the initial list of items
Iterator? filtered;
double? lowPrice;
void addLowPrice(double price) {
lowPrice=price;
rebuildFilter();
}
double? highPrice;
void addHighPrice(double price) {
highPrice=price;
rebuildFilter();
}
var sizeOptions=<String>[];
void addSizeFilter(String size) {
sizeOptions.add(size);
reubuildFilter();
}
void rebuildFilter() {
filtered=items.where((e) => e.price >= lowPrice??0 && e.price <= highPrice&&double.infinity).where((e) => sizeOptions.isEmpty || sizeOptions.contains(e));
notifyListeners();
}
void removeFilters() {
lowPrice=null;
highPrice=null;
sizeOptions.clear();
filtered=null;
notifyListeners();
}
void getFiltered() => filtered??items;
In one of my flutter app, at first I want to call an api, which will return a list of item, and the item will be shown in a ListView. I also need to call another api for each item of the ListView to fetch description of that item and show the description to each item according to their id. How can I resolve this scenario. In RxJava, there is an operator called flatmap which did the same things without any hassle. But in flutter, How can I implement this. Here is my 2 function
class HomeRepositoryImpl extends HomeRepository {
HomeGraphQLService homeGraphQLService;
HomeMapper homeMapper;
HomeRepositoryImpl(HomeGraphQLService homeGraphQLService, HomeMapper homeMapper) {
this.homeGraphQLService = homeGraphQLService;
this.homeMapper = homeMapper;
}
#override
Future<List<Course>> getAllCourseOf(String className, String groupName) async {
final response = await homeGraphQLService.getAllCourseOf(className, groupName);
return homeMapper.toCourses(response).where((course) => course.isAvailable);
}
#override
Future<CourseProgressAndPerformance> getProgressAndPerformanceAnalysisOf(String subjectCode) async {
final response = await homeGraphQLService.getProgressAndPerformanceAnalysisOf(subjectCode);
return homeMapper.toProgressAndPerformance(response);
}
}
In the above class, first I call getAllCourseOf() function to get a list of course and show them in list view. I need to call getProgressAndPerformanceAnalysisOf(courseId) to fetch description of each item and show the description in each item of that list.
So what is recommended way to do so.
thanks in advance
I'm not sure on how the listing would be presented, my guess is you're looking for Stream and asyncMap()
Here's an example implementation that would give you a list of CourseProgressAndPerformance, this is the direction I'd investigate.
var perfList = Stream
.fromIterable(listOfCourses)
.asyncMap((course) => getProgressAndPerformanceAnalysisOf(courseId))
.toList();
I'm still quite new to Flutter and I use flutter_bloc for state management.
In bloc I'm listening to a repository method Stream<List<UserAlert>> alerts(). In this method I listen both to .onChildAdded and .onChildRemoved events on a Firebase real time database node and update a local List<UserAlert> alerts, but I'm not succeeding in returning it once it updates so I get null value on bloc listener. NoSuchMethodError: The method 'listen' was called on null. I chose to manually manage it as I don't want to download the whole node at every change as it happen when listening to .onValue. I tried return alerts as Stream<List<UserAlert>> inside both firebase snapshot scopes but I get a type cast error.
I tried Stream.value(alerts) both inside each scope and as the very last thing in the method but still returns null.
How can I get alerts to be returned as a stream?
Thank you very much for your help.
Repository method:
Stream<List<UserAlert>> alerts() {
print('alerts() called');
Stream<List<UserAlert>> alertStream;
List<UserAlert> alerts = [];
// Option 2 return a list manually updated by two different events: More complicated but very efficient(bandwidth), lowest data consuming = Lowest Firebase bill.
// returns one event per node record at app start and only new afterwards
_alertSubscription = _databaseReference.onChildAdded.listen((Event event) {
print(' observer .childAdded: added alert is : ${event.snapshot.value}');
UserAlert userAlert = UserAlert.fromSnapshot(event.snapshot);
alerts.add(userAlert);
// alertStream = Stream.value(alerts); // alerts as Stream<List<UserAlert>>;
// return alerts;
print(
'observer .childAdded: alerts are: $alerts and total alerts are ${alerts.length}');
});
// returns one event per node removed record
_alertSubscription =
_databaseReference.onChildRemoved.listen((Event event) {
print(
'observer .childRemoved: removed alert is : ${event.snapshot.value}');
int index = alerts.indexWhere(
(userAlert) => userAlert.id.startsWith(event.snapshot.value['Id']));
print('index to remove is: $index');
alerts.removeAt(index);
// return alerts;
print(
'observer .childRemoved: alerts after removing are: $alerts and total alerts are ${alerts.length}');
// return alerts;
});
// yield* alertStream;
Stream.value(alerts);
}
Bloc listener:
Stream<AlertState> _mapLoadAlertToState() async* {
_alertSubscription?.cancel();
_alertSubscription = _alertRepository
.alerts()
.listen((alerts) => add(AlertsUpdated(alerts)));
//// (List<UserAlert> alerts) {
//// print('_mapLoadAlertToState() userAlterts are: $alerts');
//// add(AlertsUpdated(alerts));
// });
}
You can use the yield statement to return a Stream:
Stream<AlertState> _mapLoadAlertToState() async* {
Stream<List<UserAlert>> _stream = _alertRepository.alerts();
await for(List<UserAlert> alerts in _stream){
yield AlertsUpdated(alerts);
}
}
I changed approach and now I get data from firebase as I was expecting with previous approach.
I decided to split Repository method into two different methods that transform the streams into Stream<UserAlert :
Stream<UserAlert> addedAlert() {
print('addedAlert() called');
handleData(Event event, EventSink<UserAlert> sink) =>
sink.add(UserAlert.fromSnapshot(event.snapshot));
final transformer = StreamTransformer<Event, UserAlert>.fromHandlers(
handleData: handleData);
return _databaseReference.onChildAdded.transform(transformer);
}
Stream<UserAlert> removedAlert() {
print('removedAlert() called');
handleData(Event event, EventSink<UserAlert> sink) =>
sink.add(UserAlert.fromSnapshot(event.snapshot));
final transformer = StreamTransformer<Event, UserAlert>.fromHandlers(
handleData: handleData);
return _databaseReference.onChildRemoved.transform(transformer);
}
and handle the adding to and removing from List<UserAlert> inside the bloc method:
Stream<AlertState> _mapLoadAlertToState() async* {
_addedAlertStreamSubcription =
_alertRepository.addedAlert().listen((UserAlert alert) {
print('received snapshot is:$alert.'); // prints null
alerts.add(alert);
print(
'observer .childAdded: added alert is :$alert, we have ${alerts.length} active alerts, active alerts are: $alerts');
add(AlertsUpdated(alerts));
});
_removedAlertStreamSubscription =
_alertRepository.removedAlert().listen((UserAlert alert) {
int index =
alerts.indexWhere((userAlert) => userAlert.id.startsWith(alert.id));
print('index to remove is: $index');
alerts.removeAt(index);
print(
'observer .childRemoved: removed alert is :$alert, we have ${alerts.length} active alerts, active alerts are: $alerts');
add(AlertsUpdated(alerts));
});
}
AlertUpdated will then trigger:
Stream<AlertState> _mapAlertsUpdatedToState(AlertsUpdated alert) async* {
print(
'_mapAlertsUpdatedToState() called, alerts are: ${alert.alerts} ');
yield AlertLoaded(alert.alerts);
}
_mapAlertsUpdatedToState prints are showing the correct List, but prints from BlocListeneronly show up once with one value in the list.
BlocListener<AlertBloc, AlertState>(
listener: (BuildContext context, AlertState state) {
if (state is AlertLoaded) {
List<UserAlert> userAlerts = (state).alerts;
print(
'AlertListener userAlerts are: $userAlerts and total alerts are : ${userAlerts.length}');
}
}),
This is solved by making AlertState classes not extending Equatable as it would compare previous and new state and find them to be the same.
Thank you very much.