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),
),
],
);
}
}
StreamBuilder
orFutureBuilder
to show loading state as you fetch data from the remote DB then display the user interface once fetching is done. – Fayth