Using BLoC in a dialog: Could not find the correct Provider
Asked Answered
R

5

9

I am attempting to use a bloc to manage the content of a dialog. I am fairly new to flutter.

The page is defined as:

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text("Business Region Manager"),
    ),
    body: MultiBlocProvider(
      providers: [
        BlocProvider(
          create: (context) =>
              EntityModifyBloc(repository: widget.repository),
        ),
        BlocProvider(
          create: (context) => EntityBloc(
              repository: widget.repository,
              modifyBloc: BlocProvider.of<EntityModifyBloc>(context)),
        ),
        BlocProvider(
          create: (context) => BusinessRegionBloc(repository: widget.repository),
        ),
      ],
      child: Center(
        child: BusinessRegionPage(widget.business),
      ),
    ));
}

Inside BusinessRegionPage I make use of EntityBloc and EntityModifyBloc and, I at least think I have that correct. The page has a callback that when fired calls a method:

void onAddBusinessRegion(){
  showDialog(context: context,
    barrierDismissible: false,
    builder: (BuildContext context){
      return BusinessRegionDialog(
        positiveText: "Ok",
        onPositive: onPositive,
        negativeText: 'Cancel',
        onNegative: onNegative,
        businessId: widget.business.id,
      );
    });
}

BusinessRegionDialog is a stateful widget and the state implementation build is:

@override
Widget build(BuildContext context) {
  return BlocBuilder<BusinessRegionBloc, BusinessRegionState>(builder: (context, state) {
    if (state is BusinessRegionEmpty) {
      BlocProvider.of<BusinessRegionBloc>(context).add(GetBusinessRegions(widget.businessId));
    }
    if (state is BusinessRegionError) {
      return Center(
        child: Text('Busines Region Error'),
      );
    }
    if (state is BusinessRegionLoaded) {
      return Center(
        child: Text('BusinessRegionLoaded'),
      );
    }
    return Center(
      child: CircularProgressIndicator(),
    );
  });

when the callback is fired, I get the message:

Error: Could not find the correct Provider above this BlocBuilder<BusinessRegionBloc, BusinessRegionState> Widget

This likely happens because you used a BuildContext that does not include the provider of your choice.

I made the assumption that in the call to ShowDialog(context), the context is the same one as the one passed into build? I checked this assumption by printing the hashCode of the context before the showDialog call and after the build implementation on the BusinessRegionPage,

One of the sugestions in the error message was:

Widget build(BuildContext context) {
  return Provider<Example>(
    create: (_) => Example(),
    builder: (context) {
      return Text(context.watch<Example>()),
    }
  ),
}

so I tried:

void onAddBusinessRegion() {
  print("OnAddCallback: " + context.hashCode.toString());
  BusinessRegionClient brClient = BusinessRegionClient(client: http.Client());
  BusinessRegionRepository brRepository =
      BusinessRegionRepository(client: brClient);

  showDialog(
    context: context,
    barrierDismissible: false,
    builder: (BuildContext context) {
      return Provider<BusinessRegionBloc>(
          create: (_) => BusinessRegionBloc(repository: brRepository),
          builder: (context) {
            return BusinessRegionDialog(
              positiveText: "Ok",
              onPositive: onPositive,
              negativeText: 'Cancel',
              onNegative: onNegative,
              businessId: widget.business.id,
            );
          });
    });
}

The IDE tells me: The argument type 'BusinessRegionDialog Function(BuildContext)' can't be assigned to the parameter type 'Widget Function(BuildContext, Widget)'.

I clearly don't understand something. All I want to do is fill a dialog with information from a Future from my repository. Any guidance will be appreciated.

Rhomb answered 12/11, 2020 at 18:17 Comment(0)
S
6

Use BlocProvider.value in showDialog builder argument.

onPress: () async {
        await showDialog(
          context: context,
          builder: (_) {
            return BlocProvider.value(
              value: BlocProvider.of<BlocA>(context),
              child: SimpleDialogWidget(),
            );
          },
        );
      }
Scarlett answered 13/3, 2023 at 5:50 Comment(0)
R
3

I finally figured it out thanks to the so entry:

Flutter: bloc, how to show an alert dialog

The answer here is to use a stack to have the dialog and the page coexist and the dialog show when the listener has the correct state. The answer used a StatelessWidget to manage, in the case of the answer, SnackBar. To be able to add events to the bloc, I used a StatefulWidget.

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
    title: Text("Business Region Manager"),
    ),
    body: MultiBlocProvider(
      providers: [
        BlocProvider(
          create: (context) => EntityModifyBloc(
              repository: xbrRepository), //widget.repository),
        ),
        BlocProvider(
          create: (context) => EntityBloc(
              repository: xbrRepository, //widget.repository,
              modifyBloc: BlocProvider.of<EntityModifyBloc>(context)),
        ),
        BlocProvider(
          create: (context) => BusinessRegionDialogBloc(
              repository: xbrRepository),
        ),
      ],
      child: Stack(children: [
        BusinessRegionDialogManager(widget.business.id),
        Center(child: BusinessRegionPage(widget.business)),
      ]),
    ));
}

class BusinessRegionDialogManager extends StatefulWidget {
  final int businessId;

  BusinessRegionDialogManager(this.businessId);

  State<BusinessRegionDialogManager> createState() =>
    _BusinessRegionDialogManagerState();
}


class _BusinessRegionDialogManagerState
extends State<BusinessRegionDialogManager> {

// Callback functions from the dialog so can have access to
// BusinessRegionDialogBloc
void xnPositive(List<Region> addedRegions) {
    BlocProvider.of<BusinessRegionDialogBloc>(context)
      .add(AddBusinessRegions(addedRegions, widget.businessId));
}

void xnNegative(){
}


@override
Widget build(BuildContext context) {
  return BlocListener<BusinessRegionDialogBloc, BusinessRegionDialogState>(
    listener: (context, state) {
      if (state is RegionDialogLoaded) {
        showDialog(
          context: context,
          barrierDismissible: false,
          builder: (BuildContext context) {
            return BusinessRegionDialog(
              onPositive: xnPositive,
              onNegative: xnNegative,
              regions: state.regions,
            );
          })
      }
    },
    child: Container(),
  );
}
Rhomb answered 13/11, 2020 at 17:29 Comment(0)
D
0

i solved it as like this

what you have to do is create a provider of BlocTypeA pass it to child .create a dialog with another context2 pass the bloc with provider.value then build its child with context2 and inside the child widget access the bloc with previus context .read with BlocTypeA... hence it is solved

Dianemarie answered 12/4, 2021 at 18:10 Comment(0)
S
0

You could use a https://api.flutter.dev/flutter/widgets/Navigator-class.html widget. As explained in the https://api.flutter.dev/flutter/material/showDialog.html documentation:

The useRootNavigator argument is used to determine whether to push the dialog to the Navigator furthest from or nearest to the given context. By default, useRootNavigator is true and the dialog route created by this method is pushed to the root navigator. It can not be null.

By defining a navigator below your providers you can access them in your dialog.

Be carefull, this solution requires you to manage the navigation in your app. For exemple, if you push a new page by using Navigator.of(context) it will get you this one and not the root navigator.

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
    title: Text("Business Region Manager"),
    ),
    body: MultiBlocProvider(
      providers: [
        BlocProvider(
          create: (context) => EntityModifyBloc(
              repository: xbrRepository), //widget.repository),
        ),
        BlocProvider(
          create: (context) => EntityBloc(
              repository: xbrRepository, //widget.repository,
              modifyBloc: BlocProvider.of<EntityModifyBloc>(context)),
        ),
        BlocProvider(
          create: (context) => BusinessRegionDialogBloc(
              repository: xbrRepository),
        ),
      ],
      child: Navigator( // We add a navigator here
          onGenerateRoute: () => ShowDialogAndNeedsBlocs(),
    ));
}

class ShowDialogAndNeedsBlocs extends StatelessWidget {
@override
Widget build(BuildContext context) {
  return BlocListener<BusinessRegionDialogBloc, BusinessRegionDialogState>(
    listener: (context, state) {
      if (state is RegionDialogLoaded) {
        showDialog(
          context: context,
          barrierDismissible: false,
          useRootNavigator: false, // Here we set to false
          builder: (BuildContext context) {
            return Container() # Here we can access the blocs
          })
      }
    },
    child: Container(),
  );
}
Shaving answered 2/11, 2023 at 10:46 Comment(0)
W
0

The issue here is that the showDialog's builder gets a context from the root navigator (unless you set useRootNavigator: false) which is higher than the BlocProvider in the element tree. What you want to use is the context you were passing in to showDialog's context parameter. That one is the context from your does have the bloc above it in the element tree. So you could simply give them different names so you can refer to the outer one which

void onAddBusinessRegion() {
  print("OnAddCallback: " + context.hashCode.toString());
  BusinessRegionClient brClient = BusinessRegionClient(client: http.Client());
  BusinessRegionRepository brRepository =
      BusinessRegionRepository(client: brClient);

  showDialog(
    context: blocContext,
    barrierDismissible: false,
    builder: (BuildContext context) {
      return Provider<BusinessRegionBloc>(
          create: (_) => BusinessRegionBloc(repository: brRepository),
          builder: (context) {
            return BusinessRegionDialog(
              context: blocContext,
              positiveText: "Ok",
              onPositive: onPositive,
              negativeText: 'Cancel',
              onNegative: onNegative,
              businessId: widget.business.id,
            );
          });
    });
}

Then in your BusinessRegionDialog you can store that context in a property and use it to get the bloc you need...

   class BusinessRegionDialog ..... {  
      /// a context with the BusinessRegionBloc as an ancestor
    final BuildContext blocContext;

    BuildRegionDialog({required this.blocContext, .....);

    @override
    Widget build(BuildContext context) { // <- This context is the root Navigator and doesn't have our bloc in it :-(
      return BlocBuilder<BusinessRegionBloc, BusinessRegionState>(builder: (context, state) {
        if (state is BusinessRegionEmpty) {
          // Here we use the blocContext we saved
          BlocProvider.of<BusinessRegionBloc>(blocContext).add(GetBusinessRegions(widget.businessId));
        }
        if (state is BusinessRegionError) {
          return Center(
            child: Text('Busines Region Error'),
          );
        }
        if (state is BusinessRegionLoaded) {
          return Center(
            child: Text('BusinessRegionLoaded'),
          );
        }
        return Center(
          child: CircularProgressIndicator(),
        );
      });
Wagner answered 17/8 at 18:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.