Bloc: Bloc automatically close using BlocProvider.of<Bloc>(context)

Created on 7 Nov 2019  ·  4Comments  ·  Source: felangel/bloc

flutter_bloc 2.0.0
bloc 2.0.0

I have a Bloc (MatchBloc) with a Stream subscription.

BLOC:


import 'dart:async';

import 'package:bloc/bloc.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:equatable/equatable.dart';
import 'package:goaly/src/models/match_model.dart';
import 'package:goaly/src/repositories/match_repository.dart';
import 'package:meta/meta.dart';

class MatchBloc extends Bloc<MatchEvent, MatchState> {
  final MatchRepository _matchRepository;

  StreamSubscription _subscription;

  MatchBloc({@required MatchRepository matchRepository})
      : assert(matchRepository != null),
        _matchRepository = matchRepository;

  @override
  MatchState get initialState => MatchInitial();

  @override
  Stream<MatchState> mapEventToState(
    MatchEvent event,
  ) async* {
    if (event is MatchFetch) {
      yield* _mapToStateMatch(event);
    } else if (event is MatchListner) {
      yield MatchFetchSuccess(match: event.match);
    }
  }

  Stream<MatchState> _mapToStateMatch(MatchFetch event) async* {
    try {
      yield MatchFectching();

      match = await _matchRepository.fetchMatch(event.matchId);
      yield MatchFetchSuccess(match: match);

      // add subscription using firebase
      _subscription?.cancel();
        _subscription = match.snap.reference.snapshots().listen(
            (DocumentSnapshot snap) =>
                add(MatchListner(match: MatchModel.fromSnap(snap))));
    } catch (e) {
      yield MatchFetchError();
    }
  }


  @override
  Future<void> close() {
    // cancel subscription using firebase
    _subscription?.cancel();
    return super.close();
  }
}



Using the MatchBloc in principal Widget

...
return MaterialPageRoute(
          builder: (BuildContext context) {
            return MultiBlocProvider(
              providers: [
                BlocProvider<MatchBloc>(
                  builder: (context) =>
                      MatchBloc(matchRepository: matchRepository)
                        ..add(MatchFetch(matchId: matchId)),
                ),
                BlocProvider<MatchDetailBloc>(
                  builder: (context) =>
                      MatchDetailBloc(matchRepository: matchRepository),
                ),
              ],
              child: MatchViewScreen(),
            );
          },
        );
...

The Bloc is providing a Widget that contains a method with an AlertDialog


void _onShowSelectPlayer(){
    final _matchDetailbloc = BlocProvider.of<MatchDetailBloc>(context);
    final _matchBloc = BlocProvider.of<MatchBloc>(context);

    return showModalBottomSheet<PlayerModel>(
      backgroundColor: Colors.transparent,
      context: context,
      builder: (BuildContext context) { 

       return  MultiBlocProvider(
         providers: [
             BlocProvider<MatchDetailBloc>(
                   builder: (context) => _matchDetailbloc,
              ),
            BlocProvider<MatchBloc>(
                  builder: (context) => _matchBloc,
           ),
        ],
          child: OtherWidget(),
     );
   }
}

After closing AlertDialog, it automatically executes the MatchBloc closing method and cancels the subscription even while inside the main Widget.

@override
  Future<void> close() {
    // cancel subscription using firebase
    _subscription?.cancel();
    return super.close();
  }

[✓] Flutter (Channel stable, v1.9.1+hotfix.5, on Mac OS X 10.15.1 19B88, locale en-PA)
• Flutter version 1.9.1+hotfix.5 at /Users/juliosena/flutter
• Framework revision 1aedbb1835 (3 weeks ago), 2019-10-17 08:37:27 -0700
• Engine revision b863200c37
• Dart version 2.5.0

[✓] Android toolchain - develop for Android devices (Android SDK version 28.0.3)
• Android SDK at /Users/juliosena/Library/Android/sdk
• Android NDK location not configured (optional; useful for native profiling support)
• Platform android-28, build-tools 28.0.3
• ANDROID_HOME = /Users/juliosena/Library/Android/sdk
• Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
• Java version OpenJDK Runtime Environment (build 1.8.0_202-release-1483-b49-5587405)
• All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 11.2)
• Xcode at /Applications/Xcode.app/Contents/Developer
• Xcode 11.2, Build version 11B52
• CocoaPods version 1.8.3

[✓] Android Studio (version 3.5)
• Android Studio at /Applications/Android Studio.app/Contents
• Flutter plugin version 40.2.2
• Dart plugin version 191.8593
• Java version OpenJDK Runtime Environment (build 1.8.0_202-release-1483-b49-5587405)

[✓] VS Code (version 1.39.2)
• VS Code at /Applications/Visual Studio Code.app/Contents
• Flutter extension version 3.6.0

[✓] Connected device (1 available)
• Redmi Note 7 • 11fc7316 • android-arm64 • Android 9 (API 28)

• No issues found!

question

Most helpful comment

You can just do

MultiBlocProvider(
  providers: [
     BlocProvider<BlocA>.value(
         value: BlocProvider.of<BlocA>(context),
     ),
    BlocProvider<BlocB>.value(
         value: BlocProvider.of<BlocB>(context),
     )
  ],
  child: ChildA(),
)

All 4 comments

Hi @jcsena 👋
Thanks for opening an issue!

This is how BlocProvider is intended to work. When you use BlocProvider with a builder the BlocProvider is taking responsibility for creating the bloc and closing the bloc. In cases where you want to provide an existing bloc to a new route you should use BlocProvider.value instead as it does not automatically close the bloc.

Please refer to the documentation.

In most cases, BlocProvider should be used to build 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(
  builder: (BuildContext context) => BlocA(),
  child: ChildA(),
);

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(),
);

Copy to clipboardErrorCopied
then from either ChildA, or ScreenA we can retrieve BlocA with:

BlocProvider.of<BlocA>(context)

Hope that helps 👍

Thanks for the answering the issue.
but, actually, I have two Bloc that I need in the AlertDialog

BlocProvider.value(
  value: BlocProvider.of<BlocA>(context),
   child: BlocProvider.value(
         value: BlocProvider.of<BlocB>(context),
         child: ScreenA(),
     )
);

I think it would be nice to have

MultiBlocValue(
  values: [
     BlocProvider.value(
         value: BlocProvider.of<BlocA>(context),
     ),
    BlocProvider.value(
         value: BlocProvider.of<BlocB>(context),
     )
  ],
  child: ChildA(),
)

You can just do

MultiBlocProvider(
  providers: [
     BlocProvider<BlocA>.value(
         value: BlocProvider.of<BlocA>(context),
     ),
    BlocProvider<BlocB>.value(
         value: BlocProvider.of<BlocB>(context),
     )
  ],
  child: ChildA(),
)

Thanks again,

Was this page helpful?
0 / 5 - 0 ratings