Skip to content

Instantly share code, notes, and snippets.

@catalunha
Last active January 23, 2024 21:47
Show Gist options
  • Save catalunha/580500220454241f2a762bdf5e2954b1 to your computer and use it in GitHub Desktop.
Save catalunha/580500220454241f2a762bdf5e2954b1 to your computer and use it in GitHub Desktop.
Why use Provider for DI in BLoC

Why use Provider for DI (Dependency Injection) in BLoC

Consider this standard "BLoC" example on Dartdev (https://dartpad.dev/?id=580500220454241f2a762bdf5e2954b1)

Consider if I need the CounterBloc instance for a parameter in the constructor of my CounterView class. Which is very common in projects where I just need to pass parameters and not change the class with BLoC.

To do this, change the CounterView example to receive a parameter in the constructor (firstValue) and another that could be used internally without going through the constructor. See below.

In CounterPage0. BlocProvider works perfectly. But I don't call the CounterBloc instance in the constructor.

In CounterPage1. An error is generated because the BlocProvider does not give me an instance of CounterBloc. It doesn't even have a builder.

In CounterPage2. I can solve this problem by putting a Builder(...)

In CounterPage3. Using Provider as DI already provides me with the builder and also gives me a dispose. In other words, it is more complete.

In implementing BlocProvider, Felix Angelov, uses Provider (https://pub.dev/packages/flutter_bloc in Dependencies). So it is a strong dependency for BLoC. But it doesn't bring all the Provider features. When offering BlocProvider and MultBlocProvider.

And since BLoC is a state manager, it is getting involved with DI just to complete the package. But it wouldn't be interesting to put all your eggs in one basket. Nor use limited redeployment. As seen above.

So this is why the community uses Provider for DI and BLoC for State Manager. But it can also use any other package for DI. Which helps BLoC a lot. And it doesn't complicate it.

Using Provider for DI in BLoC is strongly recommended by the community.

// ***
// See comments at: https://gist.github.com/catalunha/580500220454241f2a762bdf5e2954b1
// ***
// This code is distributed under the MIT License.
// Copyright (c) 2018 Felix Angelov.
// You can find the original at https://github.com/felangel/bloc.
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:provider/provider.dart';
void main() {
Bloc.observer = AppBlocObserver();
runApp(const App());
}
/// Custom [BlocObserver] that observes all bloc and cubit state changes.
class AppBlocObserver extends BlocObserver {
@override
void onChange(BlocBase bloc, Change change) {
super.onChange(bloc, change);
if (bloc is Cubit) print(change);
}
@override
void onTransition(Bloc bloc, Transition transition) {
super.onTransition(bloc, transition);
print(transition);
}
}
/// {@template app}
/// A [StatelessWidget] that:
/// * uses [bloc](https://pub.dev/packages/bloc) and
/// [flutter_bloc](https://pub.dev/packages/flutter_bloc)
/// to manage the state of a counter and the app theme.
/// {@endtemplate}
class App extends StatelessWidget {
/// {@macro app}
const App({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => ThemeCubit(),
child: const AppView(),
);
}
}
/// {@template app_view}
/// A [StatelessWidget] that:
/// * reacts to state changes in the [ThemeCubit]
/// and updates the theme of the [MaterialApp].
/// * renders the [CounterPage].
/// {@endtemplate}
class AppView extends StatelessWidget {
/// {@macro app_view}
const AppView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocBuilder<ThemeCubit, ThemeData>(
builder: (_, theme) {
return MaterialApp(
theme: theme,
home: const CounterPage(),
);
},
);
}
}
/// {@template counter_page}
/// A [StatelessWidget] that:
/// * provides a [CounterBloc] to the [CounterView].
/// {@endtemplate}
/*
// CounterPage0
class CounterPage extends StatelessWidget {
/// {@macro counter_page}
const CounterPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => CounterBloc(),
child: CounterView(),
);
}
}
*/
/*
// CounterPage1
class CounterPage extends StatelessWidget {
/// {@macro counter_page}
const CounterPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => CounterBloc(),
child: CounterView(firstValue: context.read<CounterBloc>().state),
);
}
}
*/
/*
// CounterPage2
class CounterPage extends StatelessWidget {
/// {@macro counter_page}
const CounterPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => CounterBloc(),
child: Builder(
builder: (context) =>
CounterView(firstValue: context.read<CounterBloc>().state)),
);
}
}
*/
// CounterPage3
class CounterPage extends StatelessWidget {
/// {@macro counter_page}
const CounterPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Provider(
create: (_) => CounterBloc(),
builder:(context, _) => CounterView(firstValue: context.read<CounterBloc>().state),
);
}
}
/// {@template counter_view}
/// A [StatelessWidget] that:
/// * demonstrates how to consume and interact with a [CounterBloc].
/// {@endtemplate}
class CounterView extends StatelessWidget {
final int firstValue;
/// {@macro counter_view}
CounterView({
Key? key,
this.firstValue = -2,
}) : super(key: key);
late int secondValue;
@override
Widget build(BuildContext context) {
secondValue = context.read<CounterBloc>().state;
return Scaffold(
appBar: AppBar(title: Text('Counter $firstValue $secondValue')),
body: Center(
child: BlocBuilder<CounterBloc, int>(
builder: (context, count) {
return Text('$count',
style: Theme.of(context).textTheme.displayLarge);
},
),
),
floatingActionButton: Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () => context.read<CounterBloc>().add(Increment()),
),
const SizedBox(height: 4),
FloatingActionButton(
child: const Icon(Icons.remove),
onPressed: () => context.read<CounterBloc>().add(Decrement()),
),
const SizedBox(height: 4),
FloatingActionButton(
child: const Icon(Icons.brightness_6),
onPressed: () => context.read<ThemeCubit>().toggleTheme(),
),
],
),
);
}
}
/// Event being processed by [CounterBloc].
abstract class CounterEvent {}
/// Notifies bloc to increment state.
class Increment extends CounterEvent {}
/// Notifies bloc to decrement state.
class Decrement extends CounterEvent {}
/// {@template counter_bloc}
/// A simple [Bloc] that manages an `int` as its state.
/// {@endtemplate}
class CounterBloc extends Bloc<CounterEvent, int> {
/// {@macro counter_bloc}
CounterBloc() : super(0) {
on<Increment>((event, emit) => emit(state + 1));
on<Decrement>((event, emit) => emit(state - 1));
}
}
/// {@template brightness_cubit}
/// A simple [Cubit] that manages the [ThemeData] as its state.
/// {@endtemplate}
class ThemeCubit extends Cubit<ThemeData> {
/// {@macro brightness_cubit}
ThemeCubit() : super(_lightTheme);
static final _lightTheme = ThemeData.light(useMaterial3: true);
static final _darkTheme = ThemeData.dark(useMaterial3: true);
/// Toggles the current brightness between light and dark.
void toggleTheme() {
emit(state.brightness == Brightness.dark ? _lightTheme : _darkTheme);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment