I am using the flutter_bloc package to manage state in my app. I have a usecase where I have to load the initial state from the remote DB. This requires the initialState method to be async, which it is not.
If not by using the initialState method, what is the best way to load the initial state of a Bloc from a remote DB ?
Comprehensive explanation:
The initialState
of the Bloc
in the flutter_bloc
plugin must be sync.
because there must be an initial state immediately available when the bloc is instantiated.
So, if you want to have a state from an async source, you can call your async function inside of the mapEventToState
function and emit a new state when your work is completed.
General Stpes:
step(1):
Create your own Bloc class with your desired events and states.
class YourBloc extends Bloc<YourEvent, YourState> {
@override
YourState get initialState => LoadingState();
@override
Stream<YourState> mapEventToState(YourEvent event) async* {
if (event is InitEvent) {
final data = await _getDataFrom_SharedPreferences_OR_Database_OR_anyAsyncSource();
yield LoadedState(data);
}
}
}
where LoadingState
and LoadedState
can be sub classes of YourState
class or same type and can have different properties to use in widgets later.
Similarly, InitEvent
and other your events ate also sub classes of YourEvent
class or just an enum.
step(2):
Now when you wan to create BlocProvider
widget, you can immediately add the initEvent
like as the below:
BlocProvider<YourBloc>(
create: (_) => YourBloc()..add(InitEvent()),
child: YourChild(),
)
step(3):
Use different states to show different widgets:
BlocBuilder<YourBloc, YourState>(
builder: (context, state) {
if (state is LoadingState) {
return Center(child: CircularProgressIndicator(),);
}
if (state is LoadedState) {
return YourWidget(state.data);
}
}
)
Practical Example:
Please suppose we have a counter(+/-) for each product in a shopping app and we want to save the selected count of item in the SharedPreferences
or database
(you can use any async data source). so that whenever user opens the app, he/she could see the selected item counts.
//our events:
enum CounterEvent {increment, decrement, init}
class YourBloc extends Bloc<CounterEvent, int>{
final Product product;
YourBloc(int initialState, this.product) : super(initialState);
@override
Stream<int> mapEventToState(CounterEvent event) async* {
int newState;
if(event == CounterEvent.init){
//get data from your async data source (database or shared preferences or etc.)
newState = data.count;
yield newState;
}
else if(event == CounterEvent.increment){
newState = state + 1;
saveNewState(newState);
yield newState;
}else if(event == CounterEvent.decrement && state > 0){
newState = state - 1;
saveNewState(newState);
yield newState;
}
}
void saveNewState(int count){
//save your new state in database or shared preferences or etc.
}
}
class ProductCounter extends StatelessWidget {
final Product product;
ProductCounter(this.product);
@override
Widget build(BuildContext context) {
return BlocProvider<YourBloc>(
//-1 is a fake initial (sync) value that is converted to progressbar in BlocBuilder
create: (context) => YourBloc(-1, product)..add(CounterEvent.init),
child: YourWidget()
);
}
}
class YourWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final _yourBloc = BlocProvider.of<YourBloc>(context);
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () => _yourBloc.add(CounterEvent.increment),
),
BlocBuilder<ProductCounterBloc, int>(
builder: (BuildContext context, int state) {
if(state == -1){
return Center(child: CircularProgressIndicator(),);
}else {
return Container(
width: 24,
child: Text(
state > 0 ? state.toString().padLeft(2, "0") : "-",
textAlign: TextAlign.center,
),
);
}
}
),
FloatingActionButton(
child: const Icon(Icons.remove),
onPressed: () => _yourBloc.add(CounterEvent.decrement),
),
],
);
}
}
You can send an event
to the bloc
to start loading(on it event bloc send new LoadingState
) where you receive and show Loader
, then when loading ended bloc
send another `state with data and you just switch loading state to loaded(and show data). You don't need to await call, what you have to do is just pushing and receiving states
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With