简体   繁体   中英

BlocProvider.of() called with a context that does not contain a Bloc of type FicheMvtBloc

I'm developing a new Flutter Mobile app using the BLoC pattern. But I've got a problem and I don't find the solution yet.

The first one is my home page (with the MultiBlocProvider) When I press on the FloatingActionButton. It push a new screen to add a new "FicheMvt" When I hit the add button. It uses an onSave callback function to notify its parent of newly created "FicheMvt" It gives me an error.

BlocProvider.of() called with a context that does not contain a Bloc of type FicheMvtBloc.

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

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

This is the home page (render 5 tab body)

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

And this is the code of the form to add new "FicheMvt" which contains another block that manages the dynamic form (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,
        ),
      ),
    );
  }
}

This is the content of the form

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)),
                  ),
                )
              ],
            ),
          ),
        );
      },
    );
  }
}

Thanks for your help

You are using the wrong context in onSaveCallback . Here is a simplified hierarchy of your widgets:

- MaterialApp
  - EtatCollecteScreen
    - MultiBlocProvider
  - FicheMvtAddScreen

So in your onSaveCallback you are accessing the context of FicheMvtAddScreen and it's obvious from the hierarchy above that BlocProvider couldn't find the requested Bloc . It's easy to fix this:

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

I've renamed context variable to pageContext in route builder function (so it wouldn't shadow required context). Now BlocProvider should able to find requested Bloc by accessing right context.

Another way to fix is to put MultiBlocProvider higher in widgets hierarchy.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM