Get access to the last yielded state within the Flutter BloC - flutter

I have two screens: first one is list of items and second is the form page for editing or creating items. Each page is managed via dedicated BloC - ItemListBloc and FormBloc respectively, using flutter_bloc library. Whenever the submit is called on the Form I want to react to this in ItemListBloc and modify or insert item in the list to avoid refetching whole thing from the backend.
This is how I try to achieve this:
ItemListBloc({ #required this.itemFormBloc, ... }) {
itemFormSub = itemFormBloc.state.listen((state) {
if (state is FormSubmitted) {
dispatch(ModifyOrInsert(isEdit: state.isEdit, item: state.isItem));
}
});
}
#override
Stream<ItemListState> mapEventToState(
ItemListEvent event,
) async* {
if (event is ModifyOrInsert) {
yield* _modifyOrInsert(isEdit: event.isEdit, item: event.item);
}
}
Stream<ItemListState> _modifyOrInsert({bool isEdit, Item item}) async* {
// How to get access to the list of items here?
}
Unlike Redux-y global state I have number of states which I can manipulate and declare one of them current. The last state of ItemListBloc is typically ItemsLoaded which holds the whole items list. I need to get access to this state, take items, update item in list if it was an edit, prepend item to the list if it was create and silently yield updated ItemsLoaded state afterwards.

Related

Change a dropdown's items when another dropdown's value is chosen in flutter (UI wont update)

I have two drop downs, and I want to do things when they get values selected. One of those is to change the second buttondrop items based on what's selected in the first dropdown.
For example:
Dropdown1 is a list of car manufactuers
Dropdown2 is a list of their models
Dropdown1 selects mercedes
Dropdown2 gets "E Class, S Class" etc
Dropdown1 selects lexus
Dropdown2 gets "ES, LS", etc
(Eventually the second drop down will update a listview as well, but haven't gotten to that yet.)
Data wise, it works, I update the list. The problem is the UI won't update unless I do a hot reload
Currently I am just having the dropdowns fetch their data and using Future builders
Future? data1;
Future? data2;
void initState(){
super.initState();
data1 = _data1AsyncMethod();
data2 = _data2AsyncMethod();
}
_data2AsyncMethod([int? item1_id]) async{
if(item1_id == null){
item2Classes = await DefaultItems().getAllItem2Classes();
listOfItem2ClassNames = DefaultItems().returnListOfItemClassNames(item2Classes);
}
else{
// The methods below calls the DefaultItems methods which have Futures all in them.
// The getAllItems calls a network file with GET methods of future type to get data and decodes them, etc.
// They build a list of the object type, ex List<Item2>
item2Classes = await DefaultItems().getAllItem2Classes(item1_id);
listOfItem2ClassNames = DefaultItems().returnListOfItemClassNames(item2Classes);
}
}
I have this Future Builder nested in some containers and paddings
FutureBuilder{
future: data2,
builder: (context, snapshot){
if(snapshot.connectionState != done...)
// return a circle progress indictator here
else{
return CustomDropDown{
hintText: 'example hint'
dropDownType: 'name'
dropDownList: listOfItem2ClassNames
dropDownCallback: whichDropDown,
}
The onChanged in CustomDropDown passes the dropDownType and the dropDownValue
The callback
whichDropDown(String dropDownType, String dropDownValue){
if(dropDownType == 'item1'){
//so if the first dropdown was used
// some code to get item_1's id and I call the data2 method
_data2AsyncMethod(item1_id);
}
Again the data updates (listOfItem2ClassNames) BUT the UI won't update unless I hot reload. I've even called just setState without any inputs to refresh but doesn't work
So how do I get the UI to update with the data, and is my solution too convoluted in the first place? How should I solve? StreamBuilders? I was having trouble using them.
Thanks
If you do a setState in the whichDropDown function, it will rebuild the UI. Although I'm not exactly sure what you want, your question is really ambiguous.
whichDropDown(String dropDownType, String dropDownValue){
if(dropDownType == 'item1'){
//so if the first dropdown was used
// some code to get item_1's id and I call the data2 method
_data2AsyncMethod(item1_id).then((_) {
setState(() {});
});
}
}
I notice a couple things:
nothing is causing the state to update, which is what causes a rebuild. Usually this is done explicitly with a call to setState()
in whichDropdown(), you call _data2AsyncMethod(item1_id), but that is returning a new Future, not updating data2, which means your FutureBuilder has no reason to update. Future's only go from un-completed to completed once, so once the Future in the FutureBuilder has been completed, there's no reason the widget will update again.
You may want to think about redesigning this widget a bit, perhaps rather than relying on FutureBuilder, instead call setState to react to the completion of the Futures (which can be done repeatedly, as opposed to how FutureBuilder works)

Cart system in flutter bloc

I am building a shopping cart system with flutter bloc. I have a list of products and now I am adding new products to the list. It is working fine. But I want to increment the count of an item if it exists inside the list.
Stream<CartState> _mapCartItemAddedToState(
CartItemAdded event,
CartState state,
) async* {
if (state is CartLoaded) {
try {
yield CartLoaded(
cart: Cart(items: List.from(state.cart.items)..add(event.item)),
);
} on Exception {
yield CartError();
}
}
}
Stream<CartState> _mapCartItemIncrementedToState(
CartItemIncremented event, CartState state) async* {
if (state is CartLoaded) {
List<Item> items = List.from(state.cart.items);
var index = items.indexWhere((item) => item.id == event.id);
items[index].count++;
try {
yield CartLoaded(cart: Cart(items: items));
} on Exception {
yield CartError();
}
}
}
These are two states I am using. First is adding the product directly while the second reassigns the list after updating the item that inside of the list. The problem I have is my count states are not updating. I have used equatable. All dependencies are included. Is this approach correct or should I go for another way of doing it? I cannot realise how it can be done. My page is updating only after I refresh it.
Some time ago, I've built the Shopping App Prototype that uses BLoC for state management. This project solves the exact same problem. You could find the example of the Cart BLoC (together with project repository) here: https://github.com/mkobuolys/shopping-app-prototype/blob/main/lib/modules/cart/bloc/cart_bloc.dart

How to modify current bloc state like adding, updating, and deleting?

I have 2 screens, the first screen is to display a list of items. The second one is a form for creating a new item. On the server side, after each store, update, destroy action, I returns its value back to the client as a response. The goal here is to update the state without having to make a new request over the network and showing a loading screen over and over every time the user creating a new resource and going back to the screen containing the list.
But since the BlocBuilder is only depends on a specific event and state.data is only available inside an if (event is NameOfEvent) statement, I couldn't modify the current state to push the new value to it.
I found that using BlocListener combined with InitState and setState is somehow working, but I think it makes the state.data is useless inside the BlocBuilder also making the algorithm complicated for such a small task.
Is there any simpler way to achieve this condition?
child: BlocBuilder<EducationBloc, EducationState>(
builder: (context, state) {
if (state is EducationLoaded) {
return ListEducationData(educations: state.data);
}
if (state is EducationCreated) {
final education = state.data;
// i want to push [education] to <ListEducationData>
}
...
...
Create list to store data and use BlocListener so you don't need to change ui every state changes.
var _listEducations = List<EducationData>();
child : BlocListener<EducationBloc,EducationState>(
listener: (_,state){
if (state is EducationLoaded) {
setState(() { //update _listEducations
_listEducations.addAll(state.data);
});
}
},
child : Container()
)

Flutter - keep listen a stream on page change

My scenario is like this:
a BottomNavigationBar where one page contains a list of items ("listPage") and another page is a single item ("itemPage").
both pages can navigate to "itemPage" related to a different items.
the "itemPage" show details of the product and have a "Favourite" toggle button.
in the "listPage" every item shown via ListView.builder, show and can handle the "Favourite" toggle button.
I can't understand how reflect the "Favourite" change in an "itemPage" to others "itemPage" (if opened multiple time for the same item, yes, it's possible) and also to the same item in the "pageList".
I created a NotifierBloc where a BehaviorSubject > Sink is called every time a "Favourite" toggle button change its state (putting the itemId and the boolean value of the Favourite). After a database update an output PublishSubject > Stream is filled with the additional information of the item.
In this way every time a "Favourite" is toggled, all the subscribers receive the info about the flag.
BehaviorSubject<Item> inController = new BehaviorSubject<Item>();
Sink<Item> get putUpdate => inController.sink;
final PublishSubject<Item> outController = PublishSubject<Item>();
Stream<Item> get getUpdates => outController.stream;
NotifierBloc() {
inController.listen(_handleToggle);
}
_handleToggle(Item item) {
...
outController.sink.add(newItemAfterDatabaseCall);
}
In ListPage and ItemPage (both StatefulWidget) I created a StreamSubscription in DidChangeDependencies method which listen to the NotifierBloc Stream.
The StreamSubscription will be cancelled in the Dispose method.
StreamSubscription _subscription;
void didChangeDependencies() {
super.didChangeDependencies();
_subscription = _notifierBloc.getUpdates.listen((item) => {
// Do things with the Item like setState or call bloc methods
...
});
}
void dispose() {
_subscription?.cancel();
super.dispose();
}
Problems are when a new itemPage is opened: the dispose method cancel the subscription in the previous shown page, so new events will not be get listened.
In addition when a page is shown due to a previous page close, the StreamSubscription is renewed and I get updates about the last one Favourite change, but I need a list of Favourite changes, because maybe the user opened several "itemPage"s.
How can I solve?
Maybe the Stream must be passed to the Page(not the PageState)? But how to handle onData function?
You need some sort of state management system. Look into Provider. Here is a simple example of how you could use it. https://github.com/m-Skolnick/provider_example_flutter

Flutter, using a bloc in a bloc

I have two BLoCs.
EstateBloc
EstateTryBloc
My Application basically gets estates from an API and displays them in a similar fashion
Now I wanted to add a sort functionality, but I could only access the List of Estates via a specific state.
if(currentState is PostLoadedState){{
print(currentState.estates);
}
I wanted to make the List of estates available for whichever bloc, that needed that list.
What I did was, I created the EstateTryBloc, which basically contains the List of estates as a state.
class EstateTryBloc extends Bloc<EstateTryEvent, List<Estate>> {
#override
List<Estate> get initialState => [];
#override
Stream<List<Estate>> mapEventToState(
EstateTryEvent event,
) async* {
final currentState = state;
if(event is AddToEstateList){
final estates = await FetchFromEitherSource(currentState.length, 20)
.getDataFromEitherSource();
yield currentState + estates;
}
}
}
As I print the state inside the bloc I get the List of estates but I dont know how I would use that List in a different bloc.
print(EstateTryBloc().state);
simply shows the initialState.
I am open for every kind of answer, feel free to tell me if a different approach would be better.
When you do print(EstateTryBloc().state); you are creating a new instance of EstateTryBloc() that's why you always see the initialState instead of the current state.
For that to work, you must access the reference for the instance that you want to get the states of. Something like:
final EstateTryBloc bloc = EstateTryBloc();
// Use the bloc wherever you want
print(bloc.state);
Right now the recommended way to share data between blocs is to inject one bloc into another and listen for state changes. So in your case it would be something like this:
class EstateTryBloc extends Bloc<EstateTryEvent, List<Estate>> {
final StreamSubscription _subscription;
EstateTryBloc(EstateBloc estateBloc) {
_subscription = estateBloc.listen((PostState state) {
if (state is PostLoadedState) {
print(state.estates);
}
});
}
#override
Future<Function> close() {
_subscription.cancel();
return super.close();
}
}
To be honest I overcomplicated things a little bit and did not recognize the real problem.
It was that I accidently created a new instance of EstateBloc() whenever I pressed on the sort button.
Anyways, thanks for your contribution guys!