BlocProvider.of () вызывается с контекстом, который не содержит Blo c - даже если он это делает - PullRequest
3 голосов
/ 24 января 2020

Во-первых, я знаю, как BLo C должен работать, в чем заключается идея, и я знаю разницу между BlocProvider() и BlocProvider.value() конструкторами.

Для простоты мое приложение имеет 3 страницы с таким деревом виджетов:

App() => LoginPage() => HomePage() => UserTokensPage()

Я хочу, чтобы мой LoginPage() имел доступ к UserBloc потому что мне нужно войти в систему пользователя et c. Чтобы сделать это, я обертываю LoginPage() builder в App() виджет так:

void main() => runApp(App());

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'My App',
      home: BlocProvider<UserBloc>(
        create: (context) => UserBloc(UserRepository()),
        child: LoginPage(),
      ),
    );
  }
}

Это, очевидно, прекрасно работает. Затем, если Пользователь успешно войдет в систему, он перейдет к HomePage. Теперь мне нужно иметь доступ к двум разным блокам на моем HomePage, поэтому я использую MultiBlocProvider для дальнейшей передачи UserBloc и создания нового с именем DataBloc. Я делаю это так:

  @override
  Widget build(BuildContext context) {
    return BlocListener<UserBloc, UserState>(
      listener: (context, state) {
        if (state is UserAuthenticated) {
          Navigator.of(context).push(
            MaterialPageRoute<HomePage>(
              builder: (_) => MultiBlocProvider(
                providers: [
                  BlocProvider.value(
                    value: BlocProvider.of<UserBloc>(context),
                  ),
                  BlocProvider<DataBloc>(
                    create: (_) => DataBloc(DataRepository()),
                  ),
                ],
                child: HomePage(),
              ),
            ),
          );
        }
      },
[...]

Это тоже работает. Проблема возникает, когда от HomePage пользователь переходит к UserTokensPage. На UserTokensPage мне нужен уже существующий UserBloc, который я хочу передать конструктору BlocProvider.value(). Я делаю это так:

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        centerTitle: false,
        title: Text('My App'),
        actions: <Widget>[
          CustomPopupButton(),
        ],
      ),

[...]

class CustomPopupButton extends StatelessWidget {
  const CustomPopupButton({
    Key key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return PopupMenuButton<String>(
      icon: Icon(Icons.more_horiz),
      onSelected: (String choice) {
        switch (choice) {
          case PopupState.myTokens:
            {
              Navigator.of(context).push(
                MaterialPageRoute<UserTokensPage>(
                  builder: (_) => BlocProvider.value(
                    value: BlocProvider.of<UserBloc>(context),
                    child: UserTokensPage(),
                  ),
                ),
              );
            }
            break;
          case PopupState.signOut:
            {
              BlocProvider.of<UserBloc>(context).add(SignOut());
              Navigator.of(context).pop();
            }
        }
      },
[...]

Когда я нажимаю кнопку, чтобы перейти к MyTokensPage, я получаю сообщение об ошибке:

════════ Exception caught by widgets library ═══════════════════════════════════════════════════════
The following assertion was thrown building Builder(dirty):
        BlocProvider.of() called with a context that does not contain a Bloc of type UserBloc.

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

        This can happen if:
        1. The context you used comes from a widget above the BlocProvider.
        2. You used MultiBlocProvider and didn't explicity provide the BlocProvider types.

        Good: BlocProvider<UserBloc>(create: (context) => UserBloc())
        Bad: BlocProvider(create: (context) => UserBloc()).

        The context used was: CustomPopupButton

Что я делаю неправильно? Это потому, что я извлек PopupMenuButton виджет, который как-то теряет блоки? Я не понимаю, что я могу делать неправильно.

Ответы [ 4 ]

1 голос
/ 29 января 2020

Я исправил это. Внутри App виджета, который я создаю LoginPage с

home: BlocProvider<UserBloc>(
        create: (context) => UserBloc(UserRepository()),
        child: LoginPage(),

На LoginPage Я просто оборачиваю BlocBuilders одно в другое

Widget build(BuildContext context) {
    return BlocListener<UserBloc, UserState>(
      listener: (context, state) {
        if (state is UserAuthenticated) {
          Navigator.of(context).push(
            MaterialPageRoute<HomePage>(
              builder: (_) => BlocProvider.value(
                value: BlocProvider.of<UserBloc>(context),
                child: BlocProvider<NewRelicBloc>(
                  create: (_) => NewRelicBloc(NewRelicRepository()),
                  child: HomePage(),
                ),
              ),
            ),
          );
        }
      },
[...]

PopupMenuButton перемещается Пользователь до TokenPage с

              Navigator.of(context).push(
                MaterialPageRoute<UserTokensPage>(
                  builder: (_) => BlocProvider.value(
                    value: BlocProvider.of<UserBloc>(context),
                    child: UserTokensPage(),
                  ),
                ),
              );

И это решило все мои проблемы.

1 голос
/ 25 января 2020

Решение

Метод A: Доступ к UserBloc экземпляру провайдера напрямую без его передачи

Я предпочитаю это решение, так как для него требуется меньше кода.

A.1 Wrap CustomPopupButton экземпляр с поставщиком Consumer , поэтому он перестраивает себя всякий раз, когда UserBlo c уведомляет слушателей об изменении значения.

Измените это:

actions: <Widget>[
    CustomPopupButton(),
],

На:

actions: <Widget>[
    Consumer<UserBloc>(builder: (BuildContext context, UserBloc userBloc, Widget child) {
      return CustomPopupButton(),
    });
],

A.2 Вызов экземпляра провайдера изменений внутри виджета без состояния для отключения прослушивания изменений значений - «прослушивание» и результирующие «перестройки» уже выполнены Consumer.

A.2.1 Измените это:

value: BlocProvider.of<UserBloc>(context),

На:

value: BlocProvider.of<UserBloc>(context, listen: false),

A.2.2 И измените это:

BlocProvider.of<UserBloc>(context).add(SignOut());

To:

BlocProvider.of<UserBloc>(context, listen: false).add(SignOut());

Метод B: передать UserBloc экземпляр поставщика

То же, что и в методе A, но:

  • В A.1 вы ' d pass userBloc вот так: return CustomPopupButton(userBloc: userBloc),.
  • Вы бы объявили final UserBloc userBloc; свойство члена внутри CustomPopupButton.
  • В A.2 вы бы сделали это: userBloc.add(SignOut()); вместо BlocProvider.of<UserBloc>(context, listen: false).add(SignOut());
* 1 056 * Объяснение

flutter_bloc использует Provider, чтобы понять, что происходит, лучше понять поставщика. Пожалуйста, ознакомьтесь с моим ответом здесь , чтобы понять мой ответ на ваш вопрос, а также лучше понять поставщика и флаг listen.

0 голосов
/ 20 февраля 2020

Вы можете просто обернуть блоки, которые вам нужны для доступа через приложение, обернув его в точке входа приложения следующим образом:

  runApp(
      MultiBlocProvider(
          providers: [
            BlocProvider<UserBloc>(
              create: (context) =>
                  UserBloc(UserRepository()),
            ),

          ],
          child: App()
      )
  );
}

, и вы можете получить доступ к этому блоку c в любом месте ваше приложение по

BlocProvider.of<UserBloc>(context).add(event of user bloc());

0 голосов
/ 19 февраля 2020

вам не нужно использовать BlocProvider.value () для перехода на другой экран, вы можете просто поместить MaterialApp в BlocProvider как дочерний элемент этого

...