Flutter – using MultiBlocProvider but can't render BlocListener at sub screen

Issue

I am going to describe my problem and an error I have faced. And then I will copy my code for making it more clear. The problem as:

  • I am using MultiBlocProvider at root widget (StartupScreen) with declared 2 blocs are
    AuthenticationBloc, ApplicationBloc.
  • Use BlocListener<AuthenticationBloc, AuthenticationState> at root widget (StartupScreen).
  • If AuthenticationBloc’s state changes to AuthAuthenticatedState then routing to MainScreen, otherwise to LoginScreen.
  • If user has sigined in, then routing to MainScreen:
    1. I am going to get currentUser from storage (asynchronously) then I wrap BlocListener inner FutureBuilder. it end up with can’t display the screen and occur an error as following:
BlocProvider.of() called with a context that does not contain a Bloc of type ApplicationBloc.

No ancestor could be found starting from the context that was passed to BlocProvider.of<ApplicationBloc>().

This can happen if the context you used comes from a widget above the BlocProvider.

The context used was: BlocListener<ApplicationBloc, ApplicationState>(dirty, state: _BlocListenerBaseState<ApplicationBloc, ApplicationState>#1abf7(lifecycle state: created))

The relevant error-causing widget was
    FutureBuilder<UserCredentials>                       package:my_app/…/ui/main_screen.dart:43

When the exception was thrown, this was the stack
#0      BlocProvider.of                                  package:flutter_bloc/src/bloc_provider.dart:106
#1      _BlocListenerBaseState.initState                 package:flutter_bloc/src/bloc_listener.dart:160
#2      StatefulElement._firstBuild                      package:flutter/…/widgets/framework.dart:4355
#3      ComponentElement.mount                           package:flutter/…/widgets/framework.dart:4201
#4      SingleChildWidgetElementMixin.mount              package:nested/nested.dart:223
...

StartupScreen.dart

class StartupScreen extends StatelessWidget {
  final ApplicationBloc appBloc;
  final AuthenticationBloc authBloc;

  StartupScreen(this.appBloc, this.authBloc) : super();

  @override
  Widget build(BuildContext context) {
    ScreenSizeConfig().init(context);
    authBloc.add(AuthStartedEvent());

    return MultiBlocProvider(
      providers: [
        BlocProvider<ApplicationBloc>(
          create: (context) => appBloc,
        ),
        BlocProvider<AuthenticationBloc>(
          create: (context) => authBloc,
        ),
      ],
      child: BlocListener<AuthenticationBloc, AuthenticationState>(
        listener: (context, state) {
          if (state is AuthUnauthenticatedState) {
            Navigator.of(context).pushReplacementNamed(RouteConstants.LOGIN_SCREEN);
          } else if (state is AuthAuthenticatedState) {
            Navigator.of(context).pushReplacementNamed(RouteConstants.MAIN_SCREEN);
          }
        },
        child: BlocBuilder<AuthenticationBloc, AuthenticationState>(
          builder: (context, state) {
            return Center(
              child: Container(
                child: Text('Startup Screen'),
              ),
            );
          },
        ),
      ),
    );
  }
}

MainScreen.dart

class MainScreen extends StatelessWidget {
  final ApplicationBloc appBloc;
  final AuthenticationBloc authBloc;

  MainScreen(this.appBloc, this.authBloc) : super();

  @override
  Widget build(BuildContext context) {
    return _MainPageWidget(appBloc, authBloc);
  }
}

class _MainPageWidget extends StatefulWidget {
  final ApplicationBlocappBloc;
  final AuthenticationBloc authBloc;

  _MainPageWidget(this.appBloc, this.authBloc) : super();

  @override
  State<StatefulWidget> createState() => _MainPageState();
}

class _MainPageState extends State<_MainPageWidget> {
  Future<UserCredentials> getUserCredentials() async {
    return await widget.appBloc.authService.getUser();
  }

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<UserCredentials>(
        future: getUserCredentials(),
        builder: (context, snapshot) {
          if (!snapshot.hasData) {
            return Center(
              child: CircularProgressIndicator(),
            );
          } else {
            return _buildBlocListener(snapshot.data);
          }
        });
  }

  Widget _buildBlocListener(UserCredentials userCredentials) {
    return BlocListener<ApplicationBloc, ApplicationState>(
      listener: (context, state) {
        if (userCredentials.isNewUser) {
          widget.appBloc.add(AppNewUserEvent());
        } else {
          widget.appBloc
              .add(AppAlreadyCompletedNewUserProcessEvent());
        }
      },
      child: _buildBlocBuilder(context, widget.appBloc),
    );
  }

  Widget _buildBlocBuilder(BuildContext context, ApplicationBloc appBloc) {
    return BlocBuilder<ApplicationBloc, ApplicationState>(
      builder: (context, state) {
        print('main_screen.dart: go to mainscreen BlocBuilder builder: state: $state');
        return Container(
          child: Text('Main Screen'),
        );
      },
    );
  }
}

Solution

From the documentation of bloc library:

You cannot access a bloc from the same context in which it was provided so you must ensure BlocProvider.of() is called within a child BuildContext

https://bloclibrary.dev/#/faqs?id=blocproviderof-fails-to-find-bloc

You would have to take out your BlocListener and put it in a separate widget, or wrap your BlocListener with a builder widget.

Answered By – dshukertjr

Answer Checked By – Timothy Miller (FlutterFixes Admin)

Leave a Reply

Your email address will not be published.