Bloc: Question how to update different widget with different states

Created on 25 May 2020  路  8Comments  路  Source: felangel/bloc

I have three widget. One shows default Address. One is a button to show list of Address. The last widget is Address list itself. After event PageOpen fired, it will yield DefaultAddressLoaded and update first widget. When button is clicked, event OpenListAddress fired and it will yield ListAddressLoaded and update List widget.

The proble is, when state is ListAddressLoaded, default address become error because there is no address field return by the state. I think about insert both address and list address in both state, but i dont think that is the best solution. What is the best approach?

My code is something like this

This is the event
`@immutable
abstract class AddressEvent extends Equatable {
const AddressEvent();
}

class PageOpen extends AddressEvent {
const InitDefaultAddress();

@override
List get props => [];
}

class OpenListAddress extends AddressEvent {
const OpenListAddress();

@override
List get props => [];
}`

This is the state
`@immutable
abstract class AddressState extends Equatable {
const AddressState();
}

class DefaultAddressLoaded extends AddressState {
final Address address; //only return one address

const AddressLoaded(this.address);

@override
List get props => [address];
}

class ListAddressLoaded extends AddressState {
final List

list; // return list address

const ListAddressLoaded(this.list);

@override
List get props => [list];
}
`
This is my bloc

`class AddressBloc extends Bloc {
final AddressRepository addressRepository;

AddressBloc(this.addressRepository);

@override
AddressState get initialState => InitState();

@override
Stream mapEventToState(
AddressEvent event,
) async* {
if (event is PageOpen) {
yield* mapPageOpenToState();
} else if (event is OpenListAddress) {
yield* mapOpenListAddressToState();
}
}

Stream mapPageOpenToState() async* {
yield Loading();
try {
Address address = await addressRepository.getDefaultAddress();
if (address == null){
yield NoAddressLoaded();
} else {
yield DefaultAddressLoaded(address);
}
} catch (e) {
yield ErrorLoading(e.toString());
}
}

Stream mapOpenListAddressToState() async* {
yield Loading();
try {
List

list = await addressRepository.getAllAddress();
yield ListAddressLoaded(list);
} catch (e) {
yield ErrorLoading(e.toString());
}
}
}
`

And my UI is something like this
BlocBuilder<AddressBloc, AddressState>( builder: (context, state){ if (state is DefaultAddressLoaded){ AddressWidget(address: state. address); } else if (state is ListAddressLoaded){ ListAddressWidget(listaddress: state.list) } }, );

question

Most helpful comment

Thank you so much @RollyPeres . It is a nice solution using condition there. One more question, how to send value of default address to other page? Because last yield state is not always DefaultAddressLoaded. I mean how to get the variable value of default address so i can pass it to other page?

All 8 comments

Hi @ZipoNamberwan 馃憢

You'd be better of going with a single state class where you keep both the default address and the list of addresses.

Hope it helps you 馃憤

Thank you for your help @RollyPeres . The problem is, when I have error only in loading address list it also affect default address widget coz it is a single state. What I really mean is like setState to only ListAddress variable or DefaultAddress variable.

@ZipoNamberwan well depends on how you're handling those errors. If you just show a snackbar that's tied to the scaffold so it won't affect your UI.

But if you're planning to have the error message shown on each widget then you should keep separate error message fields on the state and build each widget using a BlocBuilder with a condition to only match the fields you care about(e.g.: default address + error message for it). That will ensure your default address widget won't rebuild when the list changes or errors.

You could go even more granular and create one widget for default address and a separate one for it's error message(if your requirements + UI allows for it) and properly pass the condition to each BlocBuilder to match proper field. 馃憤

Thank you so much @RollyPeres . It is a nice solution using condition there. One more question, how to send value of default address to other page? Because last yield state is not always DefaultAddressLoaded. I mean how to get the variable value of default address so i can pass it to other page?

@ZipoNamberwan glad you found it helpful.

As I mentioned earlier, you should be using a single state class and not split your state into multiple subclasses, since this particular scenario doesn't play well with that.

As always, thanks @RollyPeres for taking the time to answer! 馃檹

@ZipoNamberwan closing this for now but feel free to comment with additional questions and I鈥檓 happy to continue the conversation 馃憤

Hi @felangel @RollyPeres . I still feel confused with this bloc pattern. This is scenario i have
I have list view with the data comes from API. Every time user hit end of scroll it will fetch other data from API. I know how to do this.
But problem happen when I have to add filter in the list view. What I confuse is how to maintain filter param when user hit end of scroll.

this is my event

`@immutable
abstract class RestaurantListEvent extends Equatable {
const RestaurantListEvent();
}

class GetRestaurantList extends RestaurantListEvent {

final String address;

const GetRestaurantList(this.address);

@override
List get props => [address];
}

class LoadMore extends RestaurantListEvent {
final String address;

const LoadMore(this.address);

@override
List get props => [address];
}`

this is my state

`@immutable
abstract class RestaurantListState extends Equatable {
const RestaurantListState();
}

class LoadingRestaurantList extends RestaurantListState {

const LoadingRestaurantList();

@override
List get props => [];
}

class SuccessRestaurantList extends RestaurantListState {
final List restaurants;

const SuccessRestaurantList(this.restaurants);

@override
List get props => [restaurants];
}

class ErrorRestaurantList extends RestaurantListState {
final String message;

ErrorRestaurantList(this.message);

@override
List get props => [message];
}

class InitialRestaurantListState extends RestaurantListState {
const InitialRestaurantListState();

@override
List get props => [];
}

class NoRestaurantListAvailable extends RestaurantListState {
const NoRestaurantListAvailable();

@override
List get props => [];
}`

and this is my bloc. There is field page, sortBy and cusineType here to filter the list. How should I add this filter functionality?

class RestaurantListBloc
extends Bloc {
DataRepository repository = DataRepository();

List currentList = List();
int page = 0;
String sortBy;
List cusineType;

@override
RestaurantListState get initialState => InitialRestaurantListState();

@override
Stream mapEventToState(
RestaurantListEvent event,
) async* {
if (event is GetRestaurantList) {
yield* mapGetRestaurantListToState(event.address, page);
} else if (event is LoadMore) {
yield* mapLoadMoreToState(event.address);
}
}

Stream mapGetRestaurantListToState(
String address, int page) async* {
try {
List restaurants =
await repository.getRestaurantList(address, page);
currentList = restaurants;
if (restaurants.isEmpty) {
yield NoRestaurantListAvailable();
} else {
yield SuccessRestaurantList(currentList);
}
} catch (e) {
//yield ErrorRestaurantList(e.toString());
}
}

Stream mapLoadMoreToState(String address) async* {
page = page + 1;
try {
List restaurants =
await repository.getRestaurantList(address, page);
currentList = currentList + restaurants;
if (restaurants.isEmpty) {
yield NoRestaurantListAvailable();
} else {
yield SuccessRestaurantList(currentList);
}
} catch (e) {
//yield ErrorRestaurantList(e.toString());
}
}
}

Thx for your help

Your approach depends on what your UI should look like(is the filtered list going to replace the all items one or it's going to be shown in a different screen?) and where you're doing the filtering (client side or server side?).
But first of all, you would not want to have state inside your bloc, so fields like:

List currentList = List();
int page = 0;
String sortBy;
List cusineType;

should be in the state class.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

krusek picture krusek  路  3Comments

clicksocial picture clicksocial  路  3Comments

Reidond picture Reidond  路  3Comments

RobPFarley picture RobPFarley  路  3Comments

frankrod picture frankrod  路  3Comments