What is BLoC?
BLoC is "Business Logic Components" deceloped by Google to help separate business logic from the presentation layer and enable a developer to reuse code more efficiently.
A state management library called Bloc was created and maintained by Felix Angelo. It helps developers implement the Bloc design pattern in their Flutter application. It means that a developer must know the state of an app at any time. There should be something displayed on the screen for every interaction with the app to let users know what is happening. The gist of BLoC is that everything in the app should be represented as stream of events: widgets submit events; other widgets will respond. BLoC sits in the middle, managing the conversation. Dart even comes with syntax for working with streams that is baked into the language!
The best part about this pattern is that you won’t need to import any plugins or learn any custom syntax. Flutter already comes with everything you need.Technically, for every interaction inside the application, there should be a state emerging from it. For example, when the data is fetching, the app should be in a loading state displaying a loading animation on the screen. When the internet is off, the application should display a pop-up to let the user know there is no internet connection. MVP and MVVM are some good examples. Only the thing that will change is: BLOC will be replaced with ViewModel in MVVM.
STREAMS or REACTIVE approach. In general terms, data will be flowing from the BLOC to the UI or from UI to the BLOC in the form of streams. If you have never heard about streams.
Before diving directly into the code. Let me give you a visual experience of the architecture we will be following to structure this app.
In simple terms, BLoC accepts a stream of events, processes the data based on events, and produces the output as states. Take the simple example below:
As soon as the Rotate 90° button is clicked, the RotateEvent is dispatched to BLoC and the state representing the rotation, i.e. RotatedState, is emitted. The triangle widget rotates itself upon receiving the RotatedState from the BLoC. Similarly, the circle widget changes its color when the Change color to Red button is clicked.
Since the BLoC handles the rotation and changing color operation, both operations can be performed on any widget. This facilitates the reusability of the code.
Important BLoC concepts :
Events:-
Events tell BLoC to do something. An event can be fired from anywhere, such as from a UI widget. External events, such as changes in network connectivity, changes in sensor readings, etc., look like this:
class RotateEvent {
final double angle;
const RotateEvent(this.angle);
@override
List<Object> get props => [angle];
}
BLoC:-
BLoC is a man in the middle. All the business logic sits inside the BLoC file. It simply accepts events, performs the logic, and outputs the states. Here’s how it looks:
class TransformationBloc
extends Bloc<TransformationEvent, TransformationState> {
TransformationBloc() : super(RotatedState(angle: 0);
@override
Stream<TransformationState> mapEventToState(
TransformationEvent event) async* {
if (event is RotateEvent) {
yield RotatedState(angle: event.angle);
}
}
}
States :-
States represent the information to be processed by any widget. A widget changes itself based on the state.
class RotatedState {
final double angle;
const RotatedState({@required this.angle});
@override
List<Object> get props => [angle];
}
Cubit:-
Cubit is a simpler version of the BLoC pattern. It eliminates the need to write events.
Cubit exposes direct functions, which can result in appropriate states. Writing a Cubit instead of BLoC also reduces boilerplate code, making the code easier to read.
Here’s a simple example:
class TransformCubit extends Cubit<TransformState> {
TransformCubit() : super(RotatedState(angle: 0));
void rotate(double angle) {
emit(RotatedState(angle: angle));
}
}
Example :-
counter_bloc.dart file
import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';
part 'counter_state.dart';
enum CounterEvent { increment, decrement }
class BlocCounter extends Bloc<CounterEvent, CounterState> {
BlocCounter() : super(CounterState(counterValue: 0));
@override
Stream<CounterState> mapEventToState(CounterEvent event) async* {
switch (event) {
case CounterEvent.increment:
yield CounterState(counterValue: state.counterValue + 1);
break;
case CounterEvent.decrement:
yield CounterState(counterValue: state.counterValue - 1);
break;
}
}
}
counter_state.dart file
part of 'counter_bloc.dart';
class CounterState {
int counterValue;
CounterState({
required this.counterValue,
});
}
main.dart file
import 'package:bloc_code/bloc/counter_bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider<BlocCounter>(
create: (context) => BlocCounter(),
child: MaterialApp(
title: "Bloc Demo",
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(
title: "Bloc Demo Home Page",
),
),
);
}
}
class MyHomePage extends StatefulWidget {
final String title;
MyHomePage({required this.title});
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
widget.title,
),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("You have pushed the button this many times: "),
BlocBuilder<BlocCounter, CounterState>(
builder: (context, state) {
return Text(
state.counterValue.toString(),
style: Theme.of(context).textTheme.headline4,
);
},
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: ]
FloatingActionButton(
onPressed: () {
BlocProvider.of<BlocCounter>(context)
.add(CounterEvent.decrement);
},
tooltip: 'Decrement',
child: Icon(Icons.remove),
),
FloatingActionButton(
onPressed: () {
BlocProvider.of<BlocCounter>(context)
.add(CounterEvent.increment);
},
tooltip: 'Increment',
child: Icon(Icons.add),
)
[,
)
],
),
),
);
}
}