I use the bloc to download a list data from the network. So BlocBuilder part looks like this:
Widget buildBodyOfThePage() {
return BlocBuilder<ItemsBloc, ItemsState>(
builder: (context, state){
if(state is InitialItemsState){
return Center(
child: Text('Loading...'),
);
}
else if(state is ItemsLoadedState){ //first load, for some reasons
items = state.items;
if(state.items.length < 10)
_itemsListController.isMoreNeeded = false;
_itemsListController.isLoading = false;
return buildItemsLists(); //ListView.builder with custom widgets
}
else if(state is MoreItemsLoadedState){ //other loads
items.addAll(state.items);
if(state.items.length < 10)
_itemsListController.isMoreNeeded = false;
_itemsListController.isLoading = false;
return buildItemsLists();
}
}
);
}
All works fine, but sometimes when I scrolling I get a new last received state in BlocBuilder.builder, e.g. MoreItemsLoadedState and my list gets additional already added elements. But of course I didn't generate(with the yield) this new last state in the ItemsBloc.
As I understand, this is the expected behaviour.
But then, how can I correctly load and paginate my list data? Because this tutorial shows the way that I used.
Hi @don-prog 馃憢
Thanks for opening an issue!
The problem you're facing is due to the fact that you are maintaining state both in your widget and in your bloc. You should not need to append state.items to an items list in the widget. Instead, you should take the state.items as the complete list of items and handle the pagination and append within the bloc. BlocBuilder (and StreamBuilder, build, etc...) should not have any side effects because they can be triggered by Flutter many many times. Hope that helps! Closing for now but feel free to comment with additional questions and I'm happy to continue the conversation 馃憤
@felangel Thanks! This is what I thought. But can you share some example code for saving data in the bloc and getting it from the bloc in the widget? I want to look at the design.
@don-prog no problem! Ideally you shouldn't need to save data in your bloc. Everything you need should be part of the bloc state and can be accessed via BlocBuilder/BlocListener.
@felangel here is the example: https://github.com/ResoCoder/youtube-search-flutter-bloc/blob/master/lib/ui/search/search_bloc.dart
Here I can access the result pages using the previous state currentState. Is this the correct usage?
And how can I do this in the current bloc version? Because now I don't see currentState field.
This is using an older version so instead of currentState you can just use state.
Ok, thanks again.
@felangel I have one more related question, but I don't want to make new issues for you :)
I have a NotificationListener<ScrollNotification> on my ListView, it adds a new event(pagination request to the server) to bloc when scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent, but bloc gets a lot of these events because scroll fires multiple times. So, I need to block these events when one event is already sent. I can yield loading state in the bloc , but this does not work, because it takes a lot of time, due to bloc.add() is asynchronous and scroll has time to generate new events.
Now I use state.listIsLoading = true when scroll fires ones and check it every time. But I do this in the page widget, so I modify the state in the widget(and I also can't use final for the listIsLoading). Is this a fine solution? Or should I do something else?
Hi @don-prog i think i can answer this one :).
You can use transformations to your streams and give debounce to your events.
Take a look at this example: https://bloclibrary.dev/#/flutterinfinitelisttutorial
Using this your events will only be triggered after the debounce time
@rodrigobastosv thanks, I think this is what I need!
But I have one more case where I don't know how not to modify current state in the widget.
I have user info, which loaded from the server, e.g. state.gender. I load it to the _gender variable in the widget using BlocBuilder. After this I provide this variable in two Radios (as here).
I also need to provide onChanged method, which will write the new value to the variable _gender(not to the state.gender), so I use this:
onChanged: (Gender newValue) {
setState(() { _gender = newValue; });
},
So, when I click on the Radio it calls setState(), it change _gender variable and after that it rebuilds whole page and calls BlocBuilder which again uses old state.gender value. So, I can't change the Radio value.
Now I see only one simple solution - change the state.gender value in the page widget, and use it instead of variable _gender.
But, again, is this a fine solution or should I use something else in this case?
@don-prog you really shouldn't be maintaining two versions of state. Ideally your widgets should mostly be stateless and should only rely on the bloc state for everything. There should be no reason to maintain a private gender and call setState. Hope that helps 馃憤
@felangel ok, thanks!
state.gender value in the widget, correct? Radio without setState()? If I just use state.gender = value, not in the setState() method, then Radio widget doesn't change.@felangel my state has settings field which has gender. Settings extends Equatable, but gender in the Settings not marked as final.
Now I'm trying to modify settings.gender, send the event with settings to bloc, and bloc need just yield state with this settings.
For the first time, everything is fine, but when I try to modify gender and yield updated settings in the bloc in the second time, my BlocBuilder does not receive anything.
I think it's because bloc thinks that this is the same previous state with the previous settings object. And Equatable can't help - "Equatable is designed to only work with immutable objects so all member variables must be final".
So, what should I do? Just create a new Settings copy object every time when I have modification? How will this be done most effectively?
Regards.
@don-prog yes you should mark all fields final (states should be immutable). Always return a new copy of you state and in this case adding a copyWith is what I'd recommend 馃憤
Most helpful comment
Hi @don-prog i think i can answer this one :).
You can use transformations to your streams and give debounce to your events.
Take a look at this example: https://bloclibrary.dev/#/flutterinfinitelisttutorial
Using this your events will only be triggered after the debounce time