Augeaz
Augeaz

Reputation: 121

How to call dispose when using BLoC pattern and StatelessWidget

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

Answers (2)

Priyanshu Patra
Priyanshu Patra

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

harsha yarabarla
harsha yarabarla

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

Related Questions