You are on page 1of 24

Bloc IN FLUTTER (State Mgmt): Saturday, 29 April, 2023.

(Business Logic Component)


1. https://bloclibrary.dev/#/flutterbloccoreconcepts?
id=counter_blocdart
2. In Flutter, a BLoC (Business Logic Component) is a design pattern that helps
separate the business logic from the UI in an application. It stands for Business
Logic Component and follows the principle of separation of concerns. The BLoC
pattern is commonly used for state management in Flutter applications.
3. In the BLoC pattern, the business logic and state are encapsulated within a BLoC
class. It acts as a middle layer between the UI components and the data sources
or repositories. The BLoC receives events or user actions from the UI, processes
them, and emits new states back to the UI.
4. It is not just a State Mgmt Technique but also an Architectural
Pattern, implementing SOC.

Key features of a BLoC component in Flutter include:

5. Input Sink: It accepts input events from the UI, such as button clicks or form
submissions.
6. Output Stream: It emits new states or data to the UI through a stream. UI
components can listen to this stream and update accordingly.
7. Business Logic: It contains the business logic of the application, which processes
the events and updates the state.

8. It is NOT a STAND ALONE Architectural Pattern.


9. Three layers:
1. Presentation
2. Business Logic
3. Data
 Repository: plays with raw data & perform
transformations on data from Data Providers.
 Data Providers
10. Provider sources can be multiple.
11. Bloc units are also multiple e.g. Login ka apna Bloc and
Cart functionality ka apna Bloc.
12. Bloc to Bloc Communication is FORBIDDEN;
dependency must be avoided.
13. Bloc was designed with three core values in mind:
1. Simple: Easy to understand & can be used by developers
with varying skill levels.
2. Powerful: Help make amazing, complex applications by
composing them of smaller components.
3. Testable: Easily test every aspect of an application so that
we can iterate with confidence.

14. SEE APP AND WIDGET LIFECYCLE EVENTS


15. Bloc Widgets
1. BlocBuilder
2. BlocSelector
3. BlocProvider
4. MultiBlocProvider
5. BlocListener
6. MultiBlocListener
7. BlocConsumer
8. RepositoryProvider
9. MultiRepositoryProvider

16. Bloc Widegts:


1. Bloc Builder:
BlocBuilder is a Flutter widget which requires a Bloc and a
builder function. BlocBuilder handles building the widget in
response to new states. BlocBuilder is very similar to
StreamBuilder but has a more simple API to reduce the
amount of boilerplate code needed. The builder function will
potentially be called many times and should be a pure
function that returns a widget in response to the state.
Only specify the bloc if you wish to provide a bloc that will
be scoped to a single widget and isn't accessible via a
parent BlocProvider and the current BuildContext.

BlocBuilder<BlocA, BlocAState>(
bloc: blocA, // provide the local bloc instance
builder: (context, state) {
// return widget here based on BlocA's state
}
)

ELSE:
If the bloc parameter is omitted, BlocBuilder will
automatically perform a lookup using BlocProvider and the
current BuildContext to find the nearest Bloc:
BlocBuilder<BlocA, BlocAState>(
builder: (context, state) {
// return widget here based on BlocA's state
}
)

For fine-grained control over when the builder function is called an


optional buildWhen can be provided. buildWhen takes the previous
bloc state and current bloc state and returns a boolean.
If buildWhen returns true, builder will be called with state and the
widget will rebuild. If buildWhen returns false, builder will not be called
with state and no rebuild will occur.
BlocBuilder<BlocA, BlocAState>(
buildWhen: (previousState, state) {
// return true/false to determine whether or not
// to rebuild the widget with state
},
builder: (context, state) {
// return widget here based on BlocA's state
}
)

2. Bloc Listener:
BlocListener is a Flutter widget which takes
a BlocWidgetListener and an optional Bloc and invokes
the listener in response to state changes in the bloc. It should
be used for functionality that needs to occur once per state
change such as navigation, showing a SnackBar, showing
a Dialog, etc...
listener is only called once for each state change
(NOT including the initial state)
unlike builder in BlocBuilder and is a void function.

If the bloc parameter is omitted, BlocListener will automatically


perform a lookup using BlocProvider and the current
BuildContext.
CODE:
BlocListener<BlocA, BlocAState>(
listener: (context, state) {
// do stuff here based on BlocA's state
},
child: Container(),
)

Only specify the bloc if you wish to provide a bloc that is


otherwise not accessible via BlocProvider and the current
BuildContext.
CODE:
BlocListener<BlocA, BlocAState>(
bloc: blocA,
listener: (context, state) {
// do stuff here based on BlocA's state
},
child: Container()
)

For fine-grained control over when the listener function is


called an optional listenWhen can be
provided. listenWhen takes the previous bloc state and current
bloc state and returns a boolean. If listenWhen returns
true, listener will be called with state. If listenWhen returns
false, listener will not be called with state.
BlocListener<BlocA, BlocAState>(
listenWhen: (previousState, state) {
// return true/false to determine whether or not
// to call listener with state
},
listener: (context, state) {
// do stuff here based on BlocA's state
},
child: Container(),
)

3. Bloc Provider:
BlocProvider is a Flutter widget which provides a bloc to its
children via BlocProvider.of<T>(context). It is used as a
dependency injection (DI) widget so that a single instance of
a bloc can be provided to multiple widgets within a subtree.

In most cases, BlocProvider should be used to create new


blocs which will be made available to the rest of the subtree.
In this case, since BlocProvider is responsible for creating
the bloc, it will automatically handle closing the bloc.

BlocProvider(
create: (BuildContext context) => BlocA(),
child: ChildA(),
);
By default, BlocProvider will create the bloc lazily, meaning create will
get executed when the bloc is looked up
via BlocProvider.of<BlocA>(context).

In some cases, BlocProvider can be used to provide an existing bloc to a


new portion of the widget tree. This will be most commonly used when
an existing bloc needs to be made available to a new route. In this
case, BlocProvider will not automatically close the bloc since it did not
create it.
BlocProvider.value(
value: BlocProvider.of<BlocA>(context),
child: ScreenA(),
);

then from either ChildA, or ScreenA we can retrieve & use BlocA with:
// with extensions
context.read<BlocA>();

// without extensions
BlocProvider.of<BlocA>(context)

4. Bloc Consumer = Bloc Builder + Bloc Listener


BlocConsumer exposes a builder and listener in order to react
to new states. BlocConsumer is analogous to a nested
BlocListener and BlocBuilder but reduces the amount of
boilerplate needed. BlocConsumer should only be used when it
is necessary to both rebuild UI and execute other reactions to
state changes in the bloc. BlocConsumer takes a required
BlocWidgetBuilder and BlocWidgetListener and an optional
bloc, BlocBuilderCondition, and BlocListenerCondition.

If the bloc parameter is omitted, BlocConsumer will


automatically perform a lookup using BlocProvider and the
current BuildContext.

BlocConsumer<BlocA, BlocAState>(
listener: (context, state) {
// do stuff here based on BlocA's state
},
builder: (context, state) {
// return widget here based on BlocA's state
}
)

An optional listenWhen and buildWhen can be implemented


for more granular control over when listener and builder are
called. The listenWhen and buildWhen will be invoked on each
bloc state change. They each take the previous state and
current state and must return a bool which determines
whether or not the builder and/or listener function will be
invoked. The previous state will be initialized to the state of the
bloc when the BlocConsumer is initialized. listenWhen and
buildWhen are optional and if they aren't implemented, they
will default to true.
BlocConsumer<BlocA, BlocAState>(
listenWhen: (previous, current) {
// return true/false to determine whether or not
// to invoke listener with state
},
listener: (context, state) {
// do stuff here based on BlocA's state
},
buildWhen: (previous, current) {
// return true/false to determine whether or not
// to rebuild the widget with state
},
builder: (context, state) {
// return widget here based on BlocA's state
}
)
5. BlocSelector:
BlocSelector is a widget provided by the bloc package in
Flutter that allows you to listen to specific states emitted by
a Bloc or Cubit and rebuild parts of your widget tree based
on those states.

The BlocSelector widget takes in two main parameters:

Bloc/BlocBase or Cubit instance: This is the Bloc or Cubit


you want to listen to for state changes.

Selector function: This function is responsible for selecting


and mapping the desired state from the Bloc or Cubit state.
It takes the current state as an input and returns the desired
value(s) to be used in the builder function.

The BlocSelector widget also provides a builder parameter,


which is a callback function that gets called whenever the
selected state changes. Inside the builder function, you can
build and return the widgets that depend on the selected
state.

Here's an example of how to use BlocSelector:

BlocSelector<BlocA, StateA, SelectedState>(`


selector: (state) {
// Map the selected state from the BlocA state
return SelectedState.fromStateA(state);
},
builder: (context, selectedState) {
// Build and return the widgets based on the selected
state
return Text(selectedState.value);
},
)
In this example, BlocA is a Bloc or Cubit class, StateA is the
state emitted by BlocA, and SelectedState is a custom class
representing a selected subset of StateA or a derived value
from it.

The selector function maps the selected state


(SelectedState) from the current BlocA state. The builder
function receives the selected state (selectedState) and can
use it to build the corresponding widgets.

By using BlocSelector, you can efficiently rebuild only the


necessary parts of your widget tree in response to specific
state changes, optimizing the performance of your Flutter
application.

6. RepositoryProvider
RepositoryProvider is a Flutter widget which provides a
repository to its children via
RepositoryProvider.of<T>(context). It is used as a
dependency injection (DI) widget so that a single instance of
a repository can be provided to multiple widgets within a
subtree. BlocProvider should be used to provide blocs
whereas RepositoryProvider should only be used for
repositories.
RepositoryProvider<RepositoryA> (
create: (context) => RepositoryA(),
child: ChildA(),
);

Then from ChildA we can retrieve and use the Repository


instance with:

// with extensions
context.read<RepositoryA>();

// without extensions
RepositoryProvider.of<RepositoryA>(context)

Does Repository Provider receives a Bloc as its child widget ?


ChatGPT
No, the Repository Provider in Flutter does not receive a BLoC as its child widget. The Repository
Provider is a mechanism used to provide repository instances to the widgets in the widget tree so
that they can access data from the repositories.

In the context of Flutter and the BLoC pattern, the typical widget hierarchy would include a Provider
widget (e.g., ChangeNotifierProvider, StreamProvider, etc.) as the parent widget, which provides the
necessary dependencies, such as repositories or data sources, to its child widgets.

The BLoC (Business Logic Component) itself is typically instantiated and managed within a separate
layer, often referred to as the "BLoC layer." The BLoC layer handles the business logic and state
management, and it can make use of the provided repositories to fetch data, perform operations,
and emit states.

So, while the Repository Provider helps in providing the necessary dependencies like repositories to
the widget tree, the BLoC instances are not directly provided as children to the Repository Provider.
Instead, the BLoC instances are typically created and managed within the BLoC layer and are used by
the widgets in the widget tree as needed for state management and data flow.
1. In most cases, the root app widget will expose one or more
repositories to the subtree via RepositoryProvider.

MultiRepositoryProvider, MultiBlocProvider AND


MultiBlocListener
1. MultiRepositoryProvider:
MultiRepositoryProvider is a Flutter widget that merges multiple
RepositoryProvider widgets into one. MultiRepositoryProvider
improves the readability and eliminates the need to nest multiple
RepositoryProvider. By using MultiRepositoryProvider we can go
from:

RepositoryProvider<RepositoryA>(
create: (context) => RepositoryA(),
child: RepositoryProvider<RepositoryB>(
create: (context) => RepositoryB(),
child: RepositoryProvider<RepositoryC>(
create: (context) => RepositoryC(),
child: ChildA(),
)
)
)

TO:
MultiRepositoryProvider(
providers: [
RepositoryProvider<RepositoryA>(
create: (context) => RepositoryA(),
),
RepositoryProvider<RepositoryB>(
create: (context) => RepositoryB(),
),
RepositoryProvider<RepositoryC>(
create: (context) => RepositoryC(),
),
],
child: ChildA(),
)

2. MultiBlocProvider:
MultiBlocProvider is a Flutter widget that merges multiple
BlocProvider widgets into one. MultiBlocProvider improves the
readability and eliminates the need to nest multiple BlocProviders.
By using MultiBlocProvider we can go from:

BlocProvider<BlocA>(
create: (BuildContext context) => BlocA(),
child: BlocProvider<BlocB>(
create: (BuildContext context) => BlocB(),
child: BlocProvider<BlocC>(
create: (BuildContext context) => BlocC(),
child: ChildA(),
)
)
)

to:

MultiBlocProvider(
providers: [
BlocProvider<BlocA>(
create: (BuildContext context) => BlocA(),
),
BlocProvider<BlocB>(
create: (BuildContext context) => BlocB(),
),
BlocProvider<BlocC>(
create: (BuildContext context) => BlocC(),
),
],
child: ChildA(),
)

3. MultiBlocListener:
MultiBlocListener is a Flutter widget that merges multiple
BlocListener widgets into one. MultiBlocListener improves the
readability and eliminates the need to nest multiple BlocListeners. By
using MultiBlocListener we can go from:
BlocListener<BlocA, BlocAState>(
listener: (context, state) {},
child: BlocListener<BlocB, BlocBState>(
listener: (context, state) {},
child: BlocListener<BlocC, BlocCState>(
listener: (context, state) {},
child: ChildA(),
),
),
)

to:

MultiBlocListener(
listeners: [
BlocListener<BlocA, BlocAState>(
listener: (context, state) {},
),
BlocListener<BlocB, BlocBState>(
listener: (context, state) {},
),
BlocListener<BlocC, BlocCState>(
listener: (context, state) {},
),
],
child: ChildA(),
)

Usage
Lets take a look at how to use BlocProvider to provide a CounterBloc to
a CounterPage and react to state changes with BlocBuilder.

counter_bloc.dart
sealed class CounterEvent {}
final class CounterIncrementPressed extends CounterEvent {}
final class CounterDecrementPressed extends CounterEvent {}

class CounterBloc extends Bloc<CounterEvent, int> {


CounterBloc() : super(0)
{
on<CounterIncrementPressed>( (event, emit) => emit(state + 1) );
on<CounterDecrementPressed>( (event, emit) => emit(state - 1) );
}
}

ChatGPT Explanation:
The provided code demonstrates the usage of the sealed class, CounterEvent,
along with its subclasses CounterIncrementPressed and
CounterDecrementPressed, in conjunction with the CounterBloc class.
In this code, CounterEvent is a sealed class that serves as the base class for
different events that can occur in the CounterBloc. By making CounterEvent a
sealed class, it ensures that only the defined subclasses can be used as events
for the CounterBloc.
The CounterBloc class extends the Bloc class from the flutter_bloc package
and takes CounterEvent as the event type and int as the state type. The initial
state is set to 0 in the constructor using super(0).
Within the constructor of CounterBloc, two event handlers are defined using
the on method from the Bloc class. When a CounterIncrementPressed event
occurs, the state is updated by incrementing it by 1, and when a
CounterDecrementPressed event occurs, the state is updated by
decrementing it by 1. The emit method is used to emit the new state.
Here is an example of how this bloc can be used:

CounterBloc counterBloc = CounterBloc();


counterBloc.add(CounterIncrementPressed()); // Increment the counter
counterBloc.add(CounterDecrementPressed()); // Decrement the counter

counterBloc.stream.listen((state) {
print('Current counter value: $state');
});
In this example, an instance of CounterBloc is created. The add method is used
to dispatch events to the bloc, triggering the corresponding event handlers
and updating the state. The stream property provides access to the state
changes, and the listen method is used to listen to state updates and print the
current counter value.

main.dart
void main() => runApp(CounterApp());

class CounterApp extends StatelessWidget {


@override
Widget build(BuildContext context) {
return MaterialApp(
home: BlocProvider(
create: (_) => CounterBloc(),
child: CounterPage(),
),
);
}
}

counter_page.dart
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Counter')),
body: BlocBuilder<CounterBloc, int>(
builder: (context, count) {
return Center(
child: Text(
'$count',
style: TextStyle(fontSize: 24.0),
),
);
},
),
floatingActionButton: Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(vertical: 5.0),
child: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () =>
context.read<CounterBloc>().add(CounterIncrementPressed()),
),
),
Padding(
padding: EdgeInsets.symmetric(vertical: 5.0),
child: FloatingActionButton(
child: Icon(Icons.remove),
onPressed: () =>
context.read<CounterBloc>().add(CounterDecrementPressed()),
),
),
],
),
);
}
}
At this point we have successfully separated our presentational layer from our
business logic layer. Notice that the CounterPage widget knows nothing about
what happens when a user taps the buttons. The widget simply tells
the CounterBloc that the user has pressed either the increment or decrement
button.

RepositoryProvider Usage
We are going to take a look at how to use RepositoryProvider within the
context of the flutter_weather example.

weather_repository.dart

class WeatherRepository {
WeatherRepository({
MetaWeatherApiClient? weatherApiClient
}) : _weatherApiClient = weatherApiClient ?? MetaWeatherApiClient();

final MetaWeatherApiClient _weatherApiClient;

Future<Weather> getWeather(String city) async {


final location = await _weatherApiClient.locationSearch(city);
final woeid = location.woeid;
final weather = await _weatherApiClient.getWeather(woeid);
return Weather(
temperature: weather.theTemp,
location: location.title,
condition: weather.weatherStateAbbr.toCondition,
);
}
}
//This code is explained in FLUTTER-FAQs.docx
main.dart
import 'package:flutter/material.dart';
import 'package:flutter_weather/app.dart';
import 'package:weather_repository/weather_repository.dart';

void main() {
runApp(WeatherApp(weatherRepository: WeatherRepository()));
}
Since we only have one repository in our app, we will inject it into our widget
tree via RepositoryProvider.value. If you have more than one repository, you
can use MultiRepositoryProvider to provide multiple repository instances to
the subtree.

app.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:weather_repository/weather_repository.dart';

class WeatherApp extends StatelessWidget {


const WeatherApp({Key? key, required WeatherRepository
weatherRepository})
: _weatherRepository = weatherRepository,
super(key: key);

final WeatherRepository _weatherRepository;

@override
Widget build(BuildContext context) {
return RepositoryProvider.value(
value: _weatherRepository,
child: BlocProvider(
create: (_) => ThemeCubit(),
child: WeatherAppView(),
),
);
}
}
In most cases, the root app widget will expose one or more repositories to the
subtree via RepositoryProvider.

weather_page.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_weather/weather/weather.dart';
import 'package:weather_repository/weather_repository.dart';

class WeatherPage extends StatelessWidget {


@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => WeatherCubit(context.read<WeatherRepository>()),
child: WeatherView(),
);
}
}
Now when instantiating a bloc, we can access the instance of a repository
via context.read and inject the repository into the bloc via constructor.

Read Extension Methods from the link,


Q: What does this line means: DO use BlocBuilder instead of context.watch to explicitly scope
rebuilds.
Ans: refer Flutter FAQs.doc

You might also like