Professional Documents
Culture Documents
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.
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
}
)
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.
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.
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).
then from either ChildA, or ScreenA we can retrieve & use BlocA with:
// with extensions
context.read<BlocA>();
// without extensions
BlocProvider.of<BlocA>(context)
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
}
)
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(),
);
// with extensions
context.read<RepositoryA>();
// without extensions
RepositoryProvider.of<RepositoryA>(context)
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.
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 {}
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.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());
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();
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';
@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';