Bloc: two states in one bloc

Created on 14 Aug 2020  路  3Comments  路  Source: felangel/bloc

Is your feature request related to a problem? Please describe.

Let's say I have a bloc, and a list page, the bloc has a state which contain data list and loading status, is it possible to bind data and loading status separately?
For example, using BlocListener to notify loading status change, and show/hide loading dialog.
At the same time, using BlocBuilder to notify data change and update ui.
They share the same bloc.

I know I can divide this bloc into two blocs, one for loading status and one for data list, but the loading status has to do with business logic inside data list bloc, it turns out to be nested blocs.

Describe the solution you'd like

My problem would be solved If one bloc can have two states, and I can use BlocListener to listen to one of state, and use BlocBuilder to listen to another.

Describe alternatives you've considered
Or do you have any advice to achieve what I want?
Thanks in advance.

Additional context
N/A

question

Most helpful comment

That's exactly what I want, thanks!

There is no need for 2 states.

A bloc's "state" can be any type of object you want and isn't limited to a
single data type.

In your case, you have 2 pieces of data, a loading status and a list of data.
So by simply creating a class which contains both, you only have to worry about handling/manipulating a single state.

For example:

class MyBloc extends Cubit<MyBlocState> {
  MyBloc() : super(MyBlocState(dataStatus: DataStatus.loading)); // this is the state's initializer

  load() async {
    // inform your layout that you are loading data
    emit(state.copyWith(dataStatus: DataStatus.loading));
    var data = await getSomeData();
    // once data is loaded, add it to your state
    emit(state.copyWith(dataStatus: DataStatus.loaded, data: data));
  }
}

enum DataStatus { loading, loaded }

// this is your bloc's "state" class
class MyBlocState {
  final List<dynamic> data;
  final DataStatus dataStatus;
  MyBlocState({this.data, this.dataStatus});

  MyBlocState copyWith({
    List<dynamic> data,
    DataStatus dataStatus,
  }) =>
      MyBlocState(
        data: data ?? this.data,
        dataStatus: dataStatus ?? this.dataStatus,
      );
}

and your layout

BlocConsumer<MyBloc, MyBlocState>(
  listener: (context, state) {
    if (state.dataStatus == DataStatus.loaded) {
      // show/hide dialog
    }
  },
  builder: (context, state) {
    // 
  },
);

All 3 comments

There is no need for 2 states.

A bloc's "state" can be any type of object you want and isn't limited to a
single data type.

In your case, you have 2 pieces of data, a loading status and a list of data.
So by simply creating a class which contains both, you only have to worry about handling/manipulating a single state.

For example:

class MyBloc extends Cubit<MyBlocState> {
  MyBloc() : super(MyBlocState(dataStatus: DataStatus.loading)); // this is the state's initializer

  load() async {
    // inform your layout that you are loading data
    emit(state.copyWith(dataStatus: DataStatus.loading));
    var data = await getSomeData();
    // once data is loaded, add it to your state
    emit(state.copyWith(dataStatus: DataStatus.loaded, data: data));
  }
}

enum DataStatus { loading, loaded }

// this is your bloc's "state" class
class MyBlocState {
  final List<dynamic> data;
  final DataStatus dataStatus;
  MyBlocState({this.data, this.dataStatus});

  MyBlocState copyWith({
    List<dynamic> data,
    DataStatus dataStatus,
  }) =>
      MyBlocState(
        data: data ?? this.data,
        dataStatus: dataStatus ?? this.dataStatus,
      );
}

and your layout

BlocConsumer<MyBloc, MyBlocState>(
  listener: (context, state) {
    if (state.dataStatus == DataStatus.loaded) {
      // show/hide dialog
    }
  },
  builder: (context, state) {
    // 
  },
);

@hawkinsjb1 thanks so much for taking the time to answer, I really appreciate it! 馃檹
@Chren closing this for now but feel free to comment with additional questions and I'm happy to continue the conversation 馃憤

That's exactly what I want, thanks!

There is no need for 2 states.

A bloc's "state" can be any type of object you want and isn't limited to a
single data type.

In your case, you have 2 pieces of data, a loading status and a list of data.
So by simply creating a class which contains both, you only have to worry about handling/manipulating a single state.

For example:

class MyBloc extends Cubit<MyBlocState> {
  MyBloc() : super(MyBlocState(dataStatus: DataStatus.loading)); // this is the state's initializer

  load() async {
    // inform your layout that you are loading data
    emit(state.copyWith(dataStatus: DataStatus.loading));
    var data = await getSomeData();
    // once data is loaded, add it to your state
    emit(state.copyWith(dataStatus: DataStatus.loaded, data: data));
  }
}

enum DataStatus { loading, loaded }

// this is your bloc's "state" class
class MyBlocState {
  final List<dynamic> data;
  final DataStatus dataStatus;
  MyBlocState({this.data, this.dataStatus});

  MyBlocState copyWith({
    List<dynamic> data,
    DataStatus dataStatus,
  }) =>
      MyBlocState(
        data: data ?? this.data,
        dataStatus: dataStatus ?? this.dataStatus,
      );
}

and your layout

BlocConsumer<MyBloc, MyBlocState>(
  listener: (context, state) {
    if (state.dataStatus == DataStatus.loaded) {
      // show/hide dialog
    }
  },
  builder: (context, state) {
    // 
  },
);
Was this page helpful?
0 / 5 - 0 ratings