BlocProvider.of() вызывается с контекстом, который не содержит Bloc типа FicheMvtBloc

Я разрабатываю новое приложение Flutter Mobile с использованием шаблона BLoC. Но у меня проблема, и я пока не нахожу решения.

Первый - это моя домашняя страница (с MultiBlocProvider). Когда я нажимаю FloatingActionButton. Он нажимает новый экран, чтобы добавить новый "FicheMvt", когда я нажимаю кнопку добавления. Он использует функцию обратного вызова onSave, чтобы уведомить своего родителя о вновь созданном "FicheMvt". Это дает мне ошибку.

BlocProvider.of () вызывается с контекстом, не содержащим Bloc типа FicheMvtBloc.

Не удалось найти предка, начиная с контекста, переданного в BlocProvider.of().

Это может произойти, если используемый вами контекст исходит из виджета над BlocProvider.

Это домашняя страница (отрендерить 5 вкладок)

class EtatCollecteScreen extends StatelessWidget {
  final FicheMvtDAO ficheMvtDAO = FicheMvtDAO();
  final FicheMvtReferenceDAO ficheMvtReferenceDAO = FicheMvtReferenceDAO();

  @override
  Widget build(BuildContext context) {
    final FicheModel fiche = ModalRoute.of(context).settings.arguments;

    return MultiBlocProvider(
      providers: [
        BlocProvider<TabEtatCollecteBloc>(
          create: (context) => TabEtatCollecteBloc(),
        ),
        BlocProvider<FicheMvtBloc>(
          create: (context) => FicheMvtBloc(
            ficheMvtDAO: ficheMvtDAO,
          )..add(FicheMvtRequested(idFiche: fiche.id)),
        ),
        BlocProvider<FicheMvtReferenceBloc>(
          create: (context) => FicheMvtReferenceBloc(
            ficheMvtReferenceDAO: ficheMvtReferenceDAO,
          )..add(FicheMvtReferenceRequested(idFiche: fiche.id)),
        ),
      ],
      child: EtatCollecteContent(
        ficheModel: fiche,
      ),
    );
  }
}


class EtatCollecteContent extends StatelessWidget {
  final FicheModel ficheModel;

  const EtatCollecteContent({Key key, @required this.ficheModel});

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<TabEtatCollecteBloc, EtatCollecteTab>(
      builder: (context, activeTab) {
        return Scaffold(
          appBar: AppBar(
            title: Text("${ficheModel.id} - ${ficheModel.description}"),
            actions: <Widget>[
              RefreshMvtButton(
                visible: activeTab == EtatCollecteTab.completed,
                ficheModel: ficheModel,
              ),
              SendMvtButton(
                visible: activeTab == EtatCollecteTab.uncommitted,
                ficheModel: ficheModel,
              ),
            ],
          ),
          body: EtatCollecteBody(
            activeTab: activeTab,
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: () {
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) {
                    return FicheMvtAddScreen(onSaveCallback: (idFiche, indicateurModel, codeSite) {
                      BlocProvider.of<FicheMvtBloc>(context).add(
                        FicheMvtAdded(
                          idFiche: idFiche,
                          indicateurModel: indicateurModel,
                          codeSite: codeSite,
                        ),
                      );
                    });
                  },
                  settings: RouteSettings(
                    arguments: ficheModel,
                  ),
                ),
              );
            },
            child: Icon(Icons.add),
            tooltip: "Add",
          ),
          bottomNavigationBar: TabEtatCollecteSelector(
            activeTab: activeTab,
            onTabSelected: (tab) => BlocProvider.of<TabEtatCollecteBloc>(context).add(TabEtatCollecteUpdated(tab)),
          ),
        );
      },
    );
  }
}

А это код формы для добавления нового "FicheMvt", который содержит другой блок, который управляет динамической формой (FicheMvtAddBloc).

typedef OnSaveCallback = Function(
  int idFiche,
  IndicateurModel indicateurModel,
  String codeSite,
);

class FicheMvtAddScreen extends StatelessWidget {
  final OnSaveCallback onSaveCallback;

  const FicheMvtAddScreen({Key key, @required this.onSaveCallback}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
    final FicheModel fiche = ModalRoute.of(context).settings.arguments;
    final FicheMvtRepository ficheMvtRepository = FicheMvtRepository();

    return Scaffold(
      key: _scaffoldKey,
      appBar: new AppBar(
        title: new Text("${fiche.id} - ${fiche.description}"),
      ),
      backgroundColor: Colors.white,
      body: BlocProvider<FicheMvtAddBloc>(
        create: (context) => FicheMvtAddBloc(
          ficheMvtRepository: ficheMvtRepository,
          idFiche: fiche.id,
        )..add(NewFicheMvtFormLoaded(idFiche: fiche.id)),
        child: FicheMvtAddBody(
          ficheModel: fiche,
          onSave: onSaveCallback,
        ),
      ),
    );
  }
}

Это содержание формы

class FicheMvtAddBody extends StatefulWidget {
  final FicheModel ficheModel;

  final OnSaveCallback onSave;

  @override
  _FicheMvtAddBodyState createState() => _FicheMvtAddBodyState();

  FicheMvtAddBody({Key key, @required this.ficheModel, @required this.onSave});
}

class _FicheMvtAddBodyState extends State<FicheMvtAddBody> {
  static final GlobalKey<FormState> _formKey = GlobalKey<FormState>();

  @override
  Widget build(BuildContext context) {
    void _onIndicateurChanged(String indicateur) =>
        BlocProvider.of<FicheMvtAddBloc>(context).add(NewFicheMvtIndicateurChanged(indicateur: indicateur));
    void _onSiteChanged(String site) => BlocProvider.of<FicheMvtAddBloc>(context).add(NewFicheMvtSiteChanged(site: site));

    final FicheModel fiche = ModalRoute.of(context).settings.arguments;

    final txtIndicateur = Text("Indicateur");
    final txtSite = Text("Site");

    return BlocBuilder<FicheMvtAddBloc, FicheMvtAddState>(
      builder: (context, state) {
        return Form(
          key: _formKey,
          child: Center(
            child: ListView(
              shrinkWrap: true,
              padding: EdgeInsets.only(left: 24.0, right: 24.0),
              children: <Widget>[
                SizedBox(height: 24.0),
                txtIndicateur,
                DropdownButtonFormField<String>(
                  isExpanded: true,
                  hint: Text("Choisissez l'indicateur"),
                  value: state.indicateur?.code ?? null,
                  icon: Icon(Icons.arrow_downward),
                  iconSize: 24,
                  elevation: 16,
                  onChanged: (String newValue) {
                    _onIndicateurChanged(newValue);
                  },
                  items: state.indicateurs?.isNotEmpty == true
                      ? state.indicateurs
                          .map((CodeDescriptionModel model) => DropdownMenuItem(value: model.code, child: Text(model.description)))
                          .toList()
                      : const [],
                  validator: (value) {
                    if (value == null || value.isEmpty) {
                      return 'Entrer l\'indicateur s\'il vous plait';
                    }
                    return null;
                  },
                ),
                SizedBox(height: 24.0),
                txtSite,
                DropdownButtonFormField<String>(
                  isExpanded: true,
                  hint: Text("Choisissez le site"),
                  value: state.site?.code ?? null,
                  icon: Icon(Icons.arrow_downward),
                  iconSize: 24,
                  elevation: 16,
                  onChanged: (String newValue) {
                    _onSiteChanged(newValue);
                  },
                  items: state.sites?.isNotEmpty == true
                      ? state.sites.map((CodeDescriptionModel model) => DropdownMenuItem(value: model.code, child: Text(model.description))).toList()
                      : const [],
                  validator: (value) {
                    if (value == null || value.isEmpty) {
                      return 'Entrer le site s\'il vous plait';
                    }
                    return null;
                  },
                ),
                Padding(
                  padding: EdgeInsets.symmetric(vertical: 16.0),
                  child: RaisedButton(
                    shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(24),
                    ),
                    onPressed: () {
                      if (_formKey.currentState.validate()) {
                        

                        widget.onSave(
                          fiche.id,
                          state.indicateur,
                          state.site?.code ?? null,
                        );

            Navigator.pop(context);
                      }
                    },
                    padding: EdgeInsets.all(12),
                    color: Colors.blue,
                    child: Text('Create', style: TextStyle(color: Colors.white)),
                  ),
                )
              ],
            ),
          ),
        );
      },
    );
  }
}

Спасибо за вашу помощь

1 ответ

Решение

Вы используете неправильный контекст в onSaveCallback. Вот упрощенная иерархия ваших виджетов:

- MaterialApp
  - EtatCollecteScreen
    - MultiBlocProvider
  - FicheMvtAddScreen

Итак, в вашем onSaveCallback вы получаете доступ к контексту FicheMvtAddScreen и из иерархии выше очевидно, что BlocProvider не смог найти запрошенный Bloc. Это легко исправить:

    MaterialPageRoute(
  builder: (pageContext) {
    return FicheMvtAddScreen(onSaveCallback: (idFiche, indicateurModel, codeSite) {
      BlocProvider.of<FicheMvtBloc>(context).add(
        FicheMvtAdded(
          idFiche: idFiche,
          indicateurModel: indicateurModel,
          codeSite: codeSite,
        ),
      );
    });
  },
  settings: RouteSettings(
    arguments: ficheModel,
  ),
  ),

Я переименовал переменную контекста в pageContextв функции построителя маршрута (чтобы он не затенял требуемый контекст). Сейчас жеBlocProvider должен найти запрошенный Bloc путем доступа к правильному контексту.

Другой способ исправить - поставить MultiBlocProvider выше в иерархии виджетов.

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