RefreshIndicator is not spinning until data fetch from api with bloc in flutter

Viewed 41

I want to achieve a behaviour when user do pull to refresh, the indicator is spinning continuously until data is fetch from the network with bloc pattern. In fact i know the reason why this is happing because refresh method is returning immediately. How to block this method until data is fetched from Network. How i can await until data come from Network. Thanks!

Future<void> _refresh() async{
    context.read<PostBloc>(RefreshEvent());
}

@override
Widget build(BuildContext context) {
  return BlocBuilder<PostBloc, PostStates>(builder: (context, state){
    switch (state.status){
      case PostStatus.failure:
        return const Center(child: Text('failed to fetch posts'));
      case PostStatus.success:
        if (state.posts.isEmpty) {
          return const Center(child: Text('no posts'));
        }
        return RefreshIndicator(
          triggerMode: RefreshIndicatorTriggerMode.onEdge,
          onRefresh: () async {
            await _refresh();
          },
          child: ListView.builder(
            physics:  AlwaysScrollableScrollPhysics(),
            itemBuilder: (context, index){
              if (index >= state.posts.length){
                return BottomLoader();
              }
              return PostListItem(post: state.posts[index]);
            },
            itemCount: state.hasReachedMax ? state.posts.length : state.posts.length + 1,
            controller: _scrollController,
          ),
        );
      case PostStatus.initial:
        return const Center(child: CircularProgressIndicator());
  
    }
  });
}
1 Answers

Change this:

Future<void> _refresh() async{
  context.read<PostBloc>(RefreshEvent());
}

to this:

Future<void> _refresh() async{
  Future block = context.read<TestBloc>().stream.first;
  context.read<TestBloc>().add(TestEvent());
  await block;
}

Edit Explanation:

A bloc is just a stream which converts events to states. A BlocBuilder for example is just a modified version of a StreamBuilder. That's why we can directly listen to this stream.

The line context.read<TestBloc>().stream.first subscribes to the stream, gets the first emitted state and closes the subscription. We don't have a new state at this time. That's why get a future for now.

After this context.read<TestBloc>().add(TestEvent()) is called which actually makes your bloc to emit a new state.

Then we want to wait for the future to complete. In other words we want to wait for the state which we read with stream.first. For this to happen we have to await the future.

You may ask why we don't listen to the change after we add the event to the bloc like this:

Future<void> _refresh() async{
  context.read<TestBloc>().add(TestEvent());
  await context.read<TestBloc>().stream.first;
}

The problem is that in rare cases it's possible that our stream.first misses our PostStates and is waiting for the next new state. This will lead to an infinitely spinning RefreshIndicator. For this reason we have to split that command to go sure that we actually listen to new states before adding new events.

Related