Flutter - использует MultiBlocProvider, но не может отображать BlocListener на дополнительном экране

Я собираюсь описать свою проблему и ошибку, с которой я столкнулся. А потом скопирую свой код, чтобы было понятнее. Проблема как:

  • Я использую MultiBlocProvider в корневом виджете (StartupScreen) с объявленными двумя блоками AuthenticationBloc, ApplicationBloc.
  • Использовать BlocListener<AuthenticationBloc, AuthenticationState> в корневом виджете (StartupScreen).
  • Если состояние AuthenticationBloc изменяется на AuthAuthenticatedState, то выполняется маршрутизация на MainScreen, в противном случае - на LoginScreen.
  • Если пользователь вошел в систему, то маршрутизация на MainScreen:
    1. Я собираюсь получить currentUser из хранилища (асинхронно), а затем оборачиваю BlocListener во внутренний FutureBuilder. в конечном итоге не удается отобразить экран и возникает следующая ошибка:
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'),
        );
      },
    );
  }
}

1 ответ

Решение

Из документации блочной библиотеки:

Вы не можете получить доступ к блоку из того же контекста, в котором он был предоставлен, поэтому вы должны убедиться, что BlocProvider.of() вызывается в дочернем BuildContext

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

Вам придется вынуть свой BlocListener и поместить его в отдельный виджет или обернуть свой BlocListener виджетом-конструктором.

Другие вопросы по тегам