Reputation: 121
I am trying to understand BLoC pattern but I cannot figure out where or when to call dispose() in my example.
I am trying to understand various state management techniques in Flutter.
I came up with an example I managed to build with the use of StatefulWidget, scoped_model and streams.
I believe I finally figured out how to make my example work with the use of "BloC" pattern but I have a problem with calling the dispose() method as I use the StatelessWidgets only.
I tried converting PageOne and PageTwo to StatefulWidget and calling dispose() but ended up with closing the streams prematurely when moving between pages.
Is it possible I should not worry at all about closing the streams manually in my example?
import 'package:flutter/material.dart';
import 'dart:async';
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return StreamBuilder<ThemeData>(
initialData: bloc.themeProvider.getThemeData,
stream: bloc.streamThemeDataValue,
builder: (BuildContext context, AsyncSnapshot<ThemeData> snapshot) {
return MaterialApp(
title: 'bloc pattern example',
theme: snapshot.data,
home: BlocPatternPageOne(),
);
},
);
}
}
// -- page_one.dart
class BlocPatternPageOne extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('(block pattern) page one'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
buildRaisedButton(context),
buildSwitchStreamBuilder(),
],
),
),
);
}
StreamBuilder<bool> buildSwitchStreamBuilder() {
return StreamBuilder<bool>(
initialData: bloc.switchProvider.getSwitchValue,
stream: bloc.streamSwitchValue,
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
return Switch(
value: snapshot.data,
onChanged: (value) {
bloc.sinkSwitchValue(value);
},
);
},
);
}
Widget buildRaisedButton(BuildContext context) {
return RaisedButton(
child: Text('go to page two'),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) {
return BlocPatternPageTwo();
},
),
);
},
);
}
}
// -- page_two.dart
class BlocPatternPageTwo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('(bloc pattern) page two'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
buildRaisedButton(context),
buildSwitchStreamBuilder(),
],
),
),
);
}
StreamBuilder<bool> buildSwitchStreamBuilder() {
return StreamBuilder<bool>(
initialData: bloc.switchProvider.getSwitchValue,
stream: bloc.streamSwitchValue,
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
return Switch(
value: snapshot.data,
onChanged: (value) {
bloc.sinkSwitchValue(value);
},
);
},
);
}
Widget buildRaisedButton(BuildContext context) {
return RaisedButton(
child: Text('go back to page one'),
onPressed: () {
Navigator.of(context).pop();
},
);
}
}
// -- bloc.dart
class SwitchProvider {
bool _switchValue = false;
bool get getSwitchValue => _switchValue;
void updateSwitchValue(bool value) {
_switchValue = value;
}
}
class ThemeProvider {
ThemeData _themeData = ThemeData.light();
ThemeData get getThemeData => _themeData;
void updateThemeData(bool value) {
if (value) {
_themeData = ThemeData.dark();
} else {
_themeData = ThemeData.light();
}
}
}
class Bloc {
final StreamController<bool> switchStreamController =
StreamController.broadcast();
final SwitchProvider switchProvider = SwitchProvider();
final StreamController<ThemeData> themeDataStreamController =
StreamController();
final ThemeProvider themeProvider = ThemeProvider();
Stream get streamSwitchValue => switchStreamController.stream;
Stream get streamThemeDataValue => themeDataStreamController.stream;
void sinkSwitchValue(bool value) {
switchProvider.updateSwitchValue(value);
themeProvider.updateThemeData(value);
switchStreamController.sink.add(switchProvider.getSwitchValue);
themeDataStreamController.sink.add(themeProvider.getThemeData);
}
void dispose() {
switchStreamController.close();
themeDataStreamController.close();
}
}
final bloc = Bloc();
At the moment everything works, however, I wonder if I should worry about closing the streams manually or let Flutter handle it automatically.
If I should close them manually, when would you call dispose() in my example?
Upvotes: 11
Views: 6653
Reputation: 11
You can try this by disposing the bloc in dispose method. The code implements the bloc pattern.
import 'package:flutter/material.dart';
@RoutePage()
class CartView extends StatefulWidget {
const CartView({super.key});
@override
State<CartView> createState() => _CartViewState();
}
class _CartViewState extends State<CartView> {
final cartBloc = CartBloc();
@override
void initState() {
super.initState();
cartBloc.add(CartInitialEvent());
}
@override
void dispose() {
super.dispose();
cartBloc.close();
}
@override
Widget build(BuildContext context) {
return BlocConsumer<CartBloc, CartState>(
bloc: cartBloc,
listenWhen: (previous, current) => current is CartActionState,
listener: (context, state) {
if (state is CartErrorState) {
context.router.pop();
Utilities.flushBarErrorMessage("Couldn't delete from cart", context);
}
},
buildWhen: (previous, current) => current is! CartActionState,
builder: (context, state) {
switch (state.runtimeType) {
case CartLoadingState:
return const LoadingWidget();
case CartSuccessLoadedState:
final cartLoadedState = state as CartSuccessLoadedState;
final localOrderCartItems =
cartLoadedState.localOrderCartModel!.data;
return BuildLocalShopLoadedView(
cartItems: localOrderCartItems![0].cartItems!,
cartBloc: cartBloc,
cartTotal:
cartLoadedState.localOrderCartModel!.data![0].cartTotal,
coId: cartLoadedState.localOrderCartModel!.data![0].coId!,
);
case CartEmptyLoadedState:
return Scaffold(
// backgroundColor: Colors.g,
appBar: const CustomAppBar(
appBarTitle: "My Cart",
),
body: Padding(
padding: AppPadding.kQuatHalfPad,
child: Column(
children: [
LottieBuilder.asset(
'assets/lottie/empty-cart-animation.json'),
SizedBox(height: 20.h),
const AppSmallText(
text: 'Your Cart is Empty',
fontSize: 23,
),
SizedBox(height: 10.h),
const AppSmallText(
text:
"Looks like you haven't added anything to your cart yet",
fontWeight: FontWeight.normal,
),
SizedBox(height: 10.h),
PrimaryActionButton(
labelText: 'Start shopping',
onPressed: () {
context.router.popUntilRouteWithName(RoutesName.localShop);
context.router
.popUntilRouteWithName(LocalShopViewRoute.name);
},
),
],
),
),
);
default:
return const SizedBox();
}
},
);
}
}
class BuildLocalShopLoadedView extends StatefulWidget {
final List<CartItem>? cartItems;
final int? cartTotal;
final String coId;
final CartBloc cartBloc;
const BuildLocalShopLoadedView({
Key? key,
required this.cartItems,
required this.cartBloc,
this.cartTotal = 0,
required this.coId,
}) : super(key: key);
@override
State<BuildLocalShopLoadedView> createState() =>
_BuildLocalShopLoadedViewState();
}
class _BuildLocalShopLoadedViewState extends State<BuildLocalShopLoadedView> {
// final _razorpay = Razorpay();
String currency = '₹';
// final RazorPayIntegration _integration = RazorPayIntegration();
@override
void initState() {
super.initState();
// _integration.intiateRazorPay();
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: const CustomAppBar(
appBarTitle: "My Cart",
),
bottomNavigationBar: Padding(
padding: AppPadding.kSinglePad,
child: Container(
height: 90.h,
padding: AppPadding.kHalfPad,
decoration: BoxDecoration(
color: AppColors.kBottomSheetLightButtonColor,
borderRadius: AppBorder.kHalfCurve,
),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const AppSmallText(
text: 'Total price',
color: AppColors.kDeepAvatarColor,
),
AppSmallText(
text: '$currency${widget.cartTotal}',
color: AppColors.kDeepAvatarColor,
fontSize: 30,
),
],
),
SizedBox(
width: 10.w,
),
Expanded(
child: ElevatedButton(
onPressed: () {
//! navigate to checkout view
context.navigateTo(const CheckoutViewRoute());
},
style: ElevatedButton.styleFrom(
elevation: 0.0,
shape: RoundedRectangleBorder(
borderRadius: AppBorder.kHalfCurve,
),
backgroundColor: AppColors.kPrimaryButtonColor,
minimumSize: const Size(double.maxFinite, double.maxFinite),
),
child: const AppSmallText(
text: 'CHECKOUT',
letterSpacing: 1,
fontWeight: FontWeight.w800,
),
),
),
],
),
),
),
body: SafeArea(
child: Padding(
padding: AppPadding.kHalfHorizontal,
child: CartListViewBuilder(
cartBloc: widget.cartBloc,
cartItems: widget.cartItems,
),
),
),
);
}
}
Upvotes: 0
Reputation: 506
You can use provider package for flutter. It has callback for dispose where you can dispose of your blocs. Providers are inherited widgets and provides a clean way to manage the blocs. BTW I use stateless widgets only with provider and streams.
Upvotes: 1