BLOC
Bloc design pattern is state management system which helps us to separate our presentation and business login.
It uses state & events for management. When a event triggers, it returns new state through which we update our UI. We can update our UI by updating it’s current data or by performing any action like navigate on other screen or show dialogue on screen. If we wanna update our UI with updated data, then BlocBuilder will be used else use BlocListener.
Let’s take an example and understand how it works with state & evetns.
Suppose, we wanna fetch data from api & show it on UI. Till, we fetch data from server side, we will show a Loading text on UI & after getting data, update UI with api data.
For this, we need to trigger an event through which we tell bloc to fetch data from api.
There will be two states which return by bloc. One is used to show Loading text on UI while fetching data from server & other state is used to show api data on UI.
Read more about Flutter app development services from mobikul.
Package Installation
Add following lines in your pubspec.yaml ==>
1 2 3 |
bloc: ^7.0.0 flutter_bloc: ^7.0.0 equatable: ^0.3.0 |
Events
Create a base class for all the events. It might be possible to have more than one event which bloc will have to handle.
Name : HomeBaseEvent.dart
1 2 3 4 5 |
class HomeBaseEvent extends Equatable{ HomeBaseEvent([List props = const []]) : super(props); } |
Event 1 : To fetch data from server, create an event.
Name : FetchDataEvent.dart
1 2 3 4 5 6 7 |
class FetchDataEvent extends HomeBaseEvent { String limit; FetchDataEvent({this.limit}) : super([limit]); } |
NOTE : Here, limit is data which we pass to api method. You can change it to any variable which you wanna pass in your api method or just skip it.
States
Create a base class for all the states. It might be possible to have more than one state.
Name : HomeBaseState.dart
1 2 3 |
class HomeBaseState extends Equatable { } |
State 1 : Show Loading Text on screen while fetch data from server
Class Name : PageLoading.dart
1 2 3 |
class PageLoading extends HomeBaseState{ } |
State 2 : Show fetched data on screen
Class Name : FetchDataState.dart
1 2 3 4 5 6 7 8 9 10 11 12 |
class FetchDataState<T> extends HomeBaseState { T data; Status status; String error; FetchDataState.success({this.data}) : status = Status.success; FetchDataState.fail({this.error}) : status = Status.fail; } enum Status {success, fail} |
If we get success data from api, we will set status as success and save data in data variable. Else set fail status and save error message in error variable.
Bloc
Name : HomeBloc.dart
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
class HomeBloc extends Bloc<HomeBaseEvent, HomeBaseState> { HomeBloc() : super(null); Home_response hm; String error; final _helper = DataHelper(); @override Stream<HomeBaseState> mapEventToState(HomeBaseEvent event) async*{ if (event is FetchDataEvent) { yield PageLoading(); Either<String, Home_response> result = await _helper.getHomePageData(event.limit); result.fold( (l) { error = l; }, (r) { hm = r; }); if (error != null) yield FetchDataState.fail(error: error); if (hm != null) yield FetchDataState.success(data: hm) ; } } } |
Create a bloc class HomeBloc by extending Bloc and define its event and state. It’s mapEventToState takes event as input and return state.
Home_response is model class. You can change it according to api response.
DataHelper is my api method handling class.
getHomePageData is api method in DataHelper class which return two type of data with the help of Either class. If we get successfully data from api, then it returns Home_response else return error message as a String. To learn more about Either class, please check following link : Either Class
Here, we use yield which return state.
When event is FetchDataEvent, it returns state PageLoading. When we get result from api method getHomePageData either is success or failure, returns state FetchDataState.
View
Initially, we create an object of bloc : context.read<HomeBloc>() and trigger an event FetchDataEvent. According to states, we show Widgets.
As, I told you about blocbuilder and bloclistner in beginning of this blog. If you wanna use both in your code for same widget, then we need to use BlocConsumer.
Whenever state changes, listener & builder both will be called. To prevent this, we need to define condition in listenWhen and buildWhen block. These blocks will be called when there is new state added by bloc.
If we get FetchDataState or PageLoading as new states, we need to only update UI. So, check current state is buildWhen and return true to call builder block else return false.
As in this example, we don’t use listener block, so return false in listenWhen block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
class HomePageView extends StatelessWidget { @override Widget build(BuildContext context) { HomeBloc homeBloc = context.read<HomeBloc>(); homeBloc.add(FetchDataEvent(limit: "5")); return BlocConsumer<HomeBloc, HomeBaseState>( listener: (BuildContext context, HomeBaseState state) { }, listenWhen: (HomeBaseState prev, HomeBaseState current){ return false; }, buildWhen: (HomeBaseState prev, HomeBaseState current){ if(current is FetchDataState) { return true; } if(current is PageLoading) { return true; } return false; }, builder: (BuildContext context, HomeBaseState state) { return Scaffold( body: buildContainer(state), ); } ); } Widget buildContainer(HomeBaseState state) { if (state != null) { if (state is PageLoading) return Center( child: Text("Loading", style: TextStyle(color: Colors.red),)); if (state is FetchDataState) { if (state.status == Status.success) { return Container(); //HomePageSuccessView(state.data); } if (state.status == Status.fail) { return Container(); //HomePageFailView(state.error); } } } return Container(); } } |
In buildContainer method, we return Widget according to states.
BlocProvider
BlocProvider is responsible to create a bloc & provide it to it’s child.
1 2 3 |
BlocProvider( create: (BuildContext context) => HomeBloc(), child: HomePageView(),); |
NOTE : Here, HomeBloc is bloc and HomePageView is StatelessWidget.
If you wanna use multiple bloc in single view, then use MultiBlocProvider.
1 2 3 4 5 6 7 |
MultiBlocProvider( providers: [ BlocProvider<HomeBloc>(create: (BuildContext context) => HomeBloc()), BlocProvider<HomeBloc>(create: (BuildContext context) => HomeBloc1()), ], child: HomePageView(), ); |
NOTE : Here, HomeBloc and HomeBloc1 are bloc and HomePageView is StatelessWidget.
Hopefully, this blog will be helpful to you. If you have any query, please write it in comment section.