Bloc: Persist Previous Data For Presentation Even When State Is Loading

Created on 6 Aug 2020  路  6Comments  路  Source: felangel/bloc

Hi, I'm pretty new to Flutter world, especially the Stream stuff.

So my problem is, for example, I have a Bloc with Loaded as the current state. Therefore the UI shows the data from that state. Then the user may refresh the data (using a button, scroll up gesture, etc.) and the loading indicator will show up.

But during this Loading state, all the data on the screen is lost. This is due to the Loading state doesn't keep the data. What I want to achieve is, I'd like to still show the previous data on the screen, and then change it to new data after the state yield Loaded.

I've tried some approaches:

  • Yielding previous Loaded state right after I yield the Loading state. So the Loading and LoadSuccess state's purpose was to notify the loading indicator on the screen. For example:
final currentState = state;

yield Loading();
yield currentState;

final data = await gettingData();

yield LoadSuccess();
yield Loaded(data: data);
  • Keeping the data in the screen/widget state. So in my screen build, I have a BlocListener to listen to state changes, and each time the state changed to Loaded, I update the data in my widget state.

Those approaches can achieve what I want but I feel like it's not the right way. The first approach makes me always need to yield the latest Loaded state whenever I yield other states. And the second just makes BlocBuilder less useful since my screen will only use BlocListener and keep the data within its own widget state, no need to use builder anymore.

Could you give some recommendations on how to handle this kind of case? Or, if one of those approaches that I did is actually the right way, give me some good reasoning to make me believe it is.

question

Most helpful comment

Oh, so it works the opposite way than I thought.

Anyway, thanks for the helpful replies and explanations @RollyPeres! Hope this can help others with same case as me.

I think I can close this issue.

All 6 comments

Hi @aliftaufik 馃憢

The solution here is to have a single state class with an enum e.g.: Status { initial, loading, empty, loaded, failure }

But if you really like the approach of working with state sub-classes then you'd have to duplicate relevant fields on each class.
So in your case Loading and Loaded classes would both contain a data list, which would be copied over from one state to other, to preserve it between state changes.

Thanks for the reply @RollyPeres, but I think I missed something here.

I'd like to try using an enum for the state, but where do I keep the data in the Bloc so that my widgets can retrieve it through BlocBuilder?

While the second approach that you mentioned, I think I've tried that. But the difficulty is that in my widget, I have to use longer if-else to check each state that might hold the data to be presented. As I remembered, the dart compiler won't auto-cast state when I check it using single if with ||.

BlocBuilder<AppBloc, AppState>(
    builder: (context, state) {
        if (state is AppLoading || state is AppLoaded) {
            // state won't be auto-casted here
        }
        ...
    }
)

That makes me have to write multiple if-else to get the data. But correct me if I'm wrong.

I think the first approach makes more sense if I figured out how to keep the data.

You're right, the smart cast won't work if you use the || operator.

The first approach is indeed the recommended one.
You'll just have a single state class:

enum Status { initial, loading, empty, loaded, failure }

class MyState extends Equatable {
  const MyState({
    @required this.status,
    @required this.data,
  });

  final Status status;
  final List<int> data;

  factory MyState.initial() => MyState(
        status: Status.initial,
        data: const [],
      );

  MyState copyWith({
    Status status,
    List<int> data,
  }) =>
      MyState(
        status: status ?? this.status,
        data: data ?? this.data,
      );

  @override
  List<Object> get props => [status, data];
}

And you'd be using that status to compose your UI, e.g.:

BlocBuilder<AppBloc, AppState>(
    builder: (context, state) {
        if (state.status == Status.loaded) {
            // show list
        }
        ...
    }
)

I guess I've got the idea. This should be perfect for my use case.

One last thing to make sure, with the Equatable extended by MyState I can be sure the Bloc responses to state changes although the yielded state will always be MyState. Is that right?

Equatable deals with value equality. Basically makes sure that no two consecutive value equal states will be emitted.
Without equatable every single new state would end up in a rebuild even though it might be the same as previous one(value wise).

Oh, so it works the opposite way than I thought.

Anyway, thanks for the helpful replies and explanations @RollyPeres! Hope this can help others with same case as me.

I think I can close this issue.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

nerder picture nerder  路  3Comments

timtraversy picture timtraversy  路  3Comments

clicksocial picture clicksocial  路  3Comments

nhwilly picture nhwilly  路  3Comments

wheel1992 picture wheel1992  路  3Comments