Description
Description
We often use the BlocConsumer or BlocListener to deliver single-based events to the UI (such as: showing snackbar or dialog). On the BlocConsumer we uses the listenWhen and buildWhen methods to differentiate between such events. In the following example I show a Cubit and the corresponding implementation that we normally use:
//State class
abstract class NewsState {}
//screen based states
sealed class NewsScreenState extends NewsState {}
class NewsScreenLoadingState extends NewsScreenState {}
class NewsScreenDataState extends NewsScreenState {
final List<String> items;
NewsScreenDataState(this.items);
}
//single event based states
sealed class NewsEventState extends NewsState {}
class NewsEventLoadingError extends NewsEventState {}
class NewsEventShowInfo extends NewsEventState {
final String info;
NewsEventShowInfo(this.info);
}
//Cubit class
class NewsCubit extends Cubit<NewsState> {
NewsCubit(this._repo) : super(NewsScreenLoadingState()) {
_loadData();
}
final NewsRepository _repo;
void _loadData() async {
final data = await _repo.getNews();
emit(NewsScreenDataState(data));
}
void showInfo(String id) async {
final data = await _repo.getInfo();
if (data.isSuccess) {
emit(NewsEventShowInfo('some data'));
} else {
emit(NewsEventLoadingError());
}
}
}
//Widget implementation
class NewsWidget extends StatelessWidget {
const NewsWidget({super.key});
@override
Widget build(BuildContext context) {
return BlocConsumer<NewsCubit, NewsState>(
listenWhen: (previous, current) => current is NewsEventState,
buildWhen: (previous, current) => current is NewsScreenState,
listener: (context, state) => switch (state as NewsEventState) {
NewsEventLoadingError() => context.showSnackbar('something went wrong'),
NewsEventShowInfo(info: final info) => showDialog(
context: context,
builder: (context) => AlertDialog(title: Text(info)),
),
},
builder: (context, state) => switch (state as NewsScreenState) {
NewsScreenLoadingState() => const Center(
child: CircularProgressIndicator(),
),
NewsScreenDataState(items: final items) => ListView.builder(
itemBuilder: (context, index) => ElevatedButton(
onPressed: () => context.read<NewsCubit>().showInfo(items[index]),
child: Text(
'$index click me!',
),
),
itemCount: items.length,
),
},
);
}
}
This works fine when the bloc/cubit is responsible for the current emitted state. But if the user clicks the item and for example the "NewsEventShowInfo" state is emitted and then flutter decide to rebuild the widget we got an error because the "buildWhen" method is ignored and the switch expression of the builder method fails because the last emitted state "NewsEventShowInfo" is not a instance of the sealed NewsScreenState class.
What would be the correct procedure here? The best approach would be if the BlocConsumer implementation would emit (on rebuild) not the last emitted state but the last emitted state which passed the buildWhen method.