Hi!
I'm fairly new to the bloc pattern. I am having trouble keeping track of many properties in a single bloc. So far bloc feels very repetitive when there are multiple properties.
For example: (I have no app demo sorry)
I have a SearchResults bloc. In this bloc, I need to keep track of 3 different properties, regardless of the state. These 3 properties guide the GUI with what to display.
My state class looks like such
@immutable
abstract class SearchResultsState {
final bool prop1;
final bool prop2;
final bool prop3;
const SearchResultsState({@required this.prop1, @required this.prop2, @required this.prop3});
}
class Loaded({@required bool prop1, @required bool prop2, @required bool prop3}) : super(prop1: property1, prop2: prop2, prop3: prop3);
class Loading({@required bool prop1, @required bool prop2, @required bool prop3}) : super(prop1: property1, prop2: prop2, prop3: prop3);
class NoResults({@required bool prop1, @required bool prop2, @required bool prop3}) : super(prop1: property1, prop2: prop2, prop3: prop3);
First of all, is this correct? Does this make sense when I need to keep track of these 3 properties in all states? What if I have 8 properties, will I need to type out these constructers with all 8 properties?
Second, inside the bloc class itself (where I would handle the business logic), when I yield a new state and I want to continue keeping track of the properties, I would need to do this:
Stream<SearchResultsState> _mapSearchToState(Search search) async* {
yield Loaded(prop1: state.prop1, prop2: state.prop2, prop3: state.prop3);
await searchProvider.search();
yield Loaded(prop1: state.prop1, prop2: state.prop2, prop3: state.prop3);
}
In a lot of states, those 3 properties won't change, and it seems annoying to often type out the properties in the state object
yield Loaded(prop1: state.prop1, prop2: state.prop2, prop3: state.prop3);
How can I handle many properties in a bloc so that the bloc pattern doesn't feel like a ton of repeated code?
How would you handle this?
Hi @kevincherryholme 馃憢
Thanks for opening an issue!
It depends on the feature/use-case but in general I think it would simplify things to restructure your states like:
@immutable
abstract class SearchResultsState {}
class LoadInProgress extends SearchResultsState {}
class LoadFailure extends SearchResultsState {}
class LoadSuccess extends SearchResultsState {
LoadSuccess({@required this.prop1, @required this.prop2, @required this.prop3});
final bool prop1;
final bool prop2;
final bool prop3;
}
Then in your mapEventToState you would have something like:
Stream<SearchResultsState> _mapSearchToState(Search search) async* {
yield LoadInProgress();
try {
final results = await searchProvider.search();
yield LoadSuccess(prop1: results.prop1, prop2: results.prop2, prop3: results.prop3);
} catch (_) {
yield LoadFailure();
}
}
Let me know if that helps!
Hi!
@felangel
Thanks for the quick response!
So more specific states could help.
I do however still have a problem for my use case. All three states, LoadInProgress, LoadFailure and LoadSuccess require the 3 properties prop1, prop2 and prop3 because regardless of the state, I use these 3 properties to display different buttons. Does that make sense?
I use the type of state to define how to display my list of results, but I use the 3 properties to display different buttons on the same screen.
For example:
LoadInProgress, my screen displays a loading indicator, but the app bar is displaying buttonA because prop1 is true.LoadSuccess, my screen displays a list of search results, and the app bar is still displaying buttonA because prop1 is still true.Maybe a solution would be to create a model for the SearchResults screen, consisting of prop1, prop2 and prop3? That way I only need to manage a single object in the constructors?
If I'm not making sense please let me know, I'll do my best to create a demo :)
Hey @kevincherryholme no problem! In that case I would recommend restructuring like:
@immutable
class SearchResultsState {
const SearchResultsState({
@required this.prop1,
@required this.prop2,
@required this.prop3,
});
final bool prop1;
final bool prop2;
final bool prop3;
SearchResultsState copyWith({
bool prop1,
bool prop2,
bool prop3,
}) {
return SearchResultsState(
prop1: prop1 ?? this.prop1,
prop2: prop2 ?? this.prop2,
prop3: prop3 ?? this.prop3,
);
}
}
Then in your mapEventToState you can do something like:
Stream<SearchResultsState> _mapSearchToState(Search search) async* {
final currentState = state;
yield currentState.copyWith(...);
try {
final results = await searchProvider.search();
yield currentState.copyWith(...);
} catch (_) {
yield currentState.copyWith(...);
}
}
Let me know if that helps 馃憤
Oh! Interesting, @felangel
So we do not have to stick with the abstract state class?
Is this considered standard and proper? I want to make sure I follow good techniques.
But yeah I think this helps a lot! I'll try this out.
Thank you!!
@kevincherryholme I think this approach works for properties that are inclusive (all are possible at the same time). The first approach works well when properties/states are exclusive. Both approaches are fine and have different use-cases 馃憤
Okay, I completely understand! Thanks again, I'll keep both approaches in mind :)
Cheers.
Most helpful comment
Okay, I completely understand! Thanks again, I'll keep both approaches in mind :)
Cheers.