How to access Provided (Provider.of()) value inside showModalBottomSheet?
Asked Answered
C

10

35

I have a FloatingActionButton inside a widget tree which has a BlocProvider from flutter_bloc. Something like this:

BlocProvider(
  builder: (context) {
    SomeBloc someBloc = SomeBloc();
    someBloc.dispatch(SomeEvent());

    return someBloc;
  },
  child: Scaffold(
    body: ...
    floatingActionButton: FloatingActionButton(
      onPressed: _openFilterSchedule,
      child: Icon(Icons.filter_list),
    ),
  )
);

Which opens a modal bottom sheet:

void _openFilterSchedule() {
    showModalBottomSheet<void>(
      context: context,
      builder: (BuildContext context) {
        return TheBottomSheet();
      },
    );
  }

I am trying to access SomeBloc using BlocProvider.of<SomeBloc>(context) inside TheBottomSheet but I get the following error:

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

I have tried to use the solution described in https://mcmap.net/q/210481/-how-to-update-state-of-a-modalbottomsheet-in-flutter but only works for BottomSheet and not ModalBottomSheet.


Note: This is not restricted to BlocProvider or flutter_bloc. Any Provider from the provider package has the same behaviour.

How can I access BlocProvider.of<SomeBloc>(context) inside the showModalBottomSheet?

In case it's not possible to do that, how to adapt https://mcmap.net/q/210481/-how-to-update-state-of-a-modalbottomsheet-in-flutter solution to Modal Bottom Sheet?

Chintzy answered 18/8, 2019 at 19:36 Comment(0)
F
60

InheritedWidgets, and therefore Providers, are scoped to the widget tree. They cannot be accessed outside of that tree.

The thing is, using showDialog and similar functions, the dialog is located in a different widget tree – which may not have access to the desired provider.

It is therefore necessary to add the desired providers in that new widget tree:

void myShowDialog() {
  final myModel = Provider.of<MyModel>(context, listen: false);
  showDialog(
    context: context,
    builder: (_) {
      return Provider.value(value: myModel, child: SomeDialog());
    },
  );
}
Fayum answered 18/8, 2019 at 20:41 Comment(8)
Is there anything to worry with this implementation? It seem strange provide 2 time.Clock
@Clock no. It's totally logical and desiredYandell
But is the 2 provider different? For example, is same variable access in both provider? I am implement now and it seem if I call method from new provider this is not notifylisteners in other widget treeClock
@Clock you have to use the .value constructor, by passing it the existing variable.Yandell
If I do this it show Unhandled Exception: Error: Could not find the correct Provider<Model> above this Page WidgetClock
it doesn't work for me, it says that the provider is called before it disposed, it seems that we have to add the provider in the top of material app as the modal bottom sheet is in a separate widget tree under material app directly as stated in second answerSlighting
How to access provider value inside SomeDialog() widget ?Upshaw
Awesome!! I think it has a same pattern to BLoC so implemented it as well final blocValue = BlocProvider.of<EcommerceBloc>(context, listen: false); then on builder return BlocProvider.value(value: blocValue, child: const MyWidget()); Thanks man, i've been looking for this way quite longBysshe
B
14

Provider in showModalBottomSheet (Bottom-Sheet)

void myBottomSheet() {
  final myModel = Provider.of<MyModel>(context, listen: false);
  showModalBottomShee(
    context: context,
    builder: (_) {
      return ListenableProvider.value(
        value: myModel,
        child: Text(myModel.txtValue),
      );
    },
  );
}
Bianka answered 26/8, 2020 at 7:52 Comment(1)
Yes, this is the solution if you want to listen to state changes in the bottom sheet.Heterothallic
P
7

You need move Provider to top layer(MaterialApp)

According to picture, Dialog widget is under MaterialApp, so this is why you using wrong context

enter image description here

Polypody answered 18/2, 2020 at 2:20 Comment(1)
Correct answer.Caroche
H
2

I used ChangeNotifierProvider from provider and used ChangeNotifierProvider.value in the bottomsheet.

NOTE:
It's important to make sure to use the provider's builder context:
builder: (context, _) {}

ChangeNotifierProvider(
  create: (context) => MyProvider(),
  builder: (context, _) {
    return Scaffold(
      body: ...
      floatingActionButton: FloatingActionButton(
        onPressed: () => _openFilterSchedule(context),
        child: Icon(Icons.filter_list),
      ),
    ),
  },
);

The bottom sheet:

void _openFilterSchedule(BuildCOntext context) {
  showModalBottomSheet<void>(
    context: context,
    builder: (_) {
      return ChangeNotifierProvider<MyProvider>.value(
        value: context.watch<MyProvider>(),
        builder: (context, _) {
          final List<String> sampleDataToWatch =
              context.watch<MyProvider>().myData;

          if (sampleDataToWatch.isEmpty) {
            return const Center(child: CircularProgressIndicator());
          } else {
            return Column(
              children: sampleDataToWatch.map((name) => Text(name)).toList(),
            );
          }
        },
      );
    },
  );
}

Just a sample provider and the future changes:

class MyProvider extends ChangeNotifier {
  List<String> myData = [];

  MyProvider() {
    loadNames();
  }

  void loadNames() {
    Future.delayed(
      const Duration(seconds: 3),
      () {
        myData.addAll(['John', 'Maria', 'Lewis', 'Danny']);
        notifyListeners();
      },
    );
  }
}
Heterothallic answered 6/5, 2023 at 2:1 Comment(1)
This worked for me! The context.watch<HomeNotifier>() in the ChangeNotifierProvider<MyProvider>.value value and the when getting the data. I was missing context.watch<MyProvider>().myData I assigned it directly as notifier.myData and didn't have the watch part. After adding that it worked perfectly!Mika
S
1

wrap your whole child widget inside the consumer.

void myShowDialog() {
  showDialog(
    context: context,
    builder: Consumer<MyModel>(
        builder: (context, value, builder) {
        retuen widget(); 
      }
  );
}
Suburban answered 11/6, 2022 at 2:16 Comment(0)
S
0

You should split Scaffold widget and its children, to another StatefulWidget

From single Widget

class MainScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      builder: (context) {
        SomeBloc someBloc = SomeBloc();
        someBloc.dispatch(SomeEvent());
        return someBloc;
      },
      child: Scaffold(
        body: ...
        floatingActionButton: FloatingActionButton(
          onPressed: _openFilterSchedule,
          child: Icon(Icons.filter_list),
        ),
      )
    );
  }
}

Splitted into these two widget

class MainScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      builder: (context) {
        SomeBloc someBloc = SomeBloc();
        someBloc.dispatch(SomeEvent());
        return someBloc;
      },
      child: Screen(),
    );
  }
}

and ..

class Screen extends StatelessWidget {

  void _openFilterSchedule() {
    showModalBottomSheet<void>(
      context: context,
      builder: (BuildContext context) {
        return TheBottomSheet();
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ...
      floatingActionButton: FloatingActionButton(
        onPressed: _openFilterSchedule,
        child: Icon(Icons.filter_list),
      ),
    );
  }
}
Skees answered 18/8, 2019 at 19:58 Comment(1)
I had to change Screen to Stateful in order to use showModalBottomSheet, but it does not work. Same error is throwed inside TheBottomSheet.Chintzy
B
0

I found a solution, Just return your showModalBottomSheet with a StatefulBuilder and use the context of your modalsheet builder to pass to your provider. a snippet of my code below:

Future<Widget> showModal(int qty, Product product) async {
    return await showModalBottomSheet(
        isScrollControlled: true,
        backgroundColor: Colors.transparent,
        context: context,
        builder: (BuildContext ctx) {
          return StatefulBuilder(builder: (ctx, state) {
             return Container(
                  child: RaisedButton(
                          onPressed: () {
                             Product prod = Product(product.id, 
                                         product.sku, product.name, qty);
                             Provider.of<CartProvider>(ctx, listen: 
                                        false).addCart(prod);}),);
    }
  }
);
}
Blithesome answered 18/8, 2020 at 3:33 Comment(0)
J
0

Not finding a clear explanation of adding multiple provided values, I thought I'd share here for reference.

   await showMobileModals(
    isDismissible: false,
    context: context,
    child: MultiProvider(
      providers: [
        Provider.value(
          value: provided_one,
        ),
        Provider.value(
          value: provided_two,
        ),
        Provider.value(
          value: provided_three,
        ),
      ],
      child: Container(),
    ),
  );
Joshuajoshuah answered 26/4, 2022 at 19:25 Comment(0)
P
-1

TLDR: Make sure your import statement's casings match your project's folder casings.

I came across one other quirk while debugging this same error. I had several providers that were all working, including in showModalBottomSheets, however one was not working. After combing through the entire widget tree, without finding any discrepancies, I found that I had capitalized the first letter of a folder on one of the import statements of my problem-child notifier. I think this confused the compiler and caused it to throw the Could not find the correct Provider above this widget error.

After ensuring the import statement casing matched the folder name, my provider problems were resolved. Hopefully this will save someone a headache.

Pehlevi answered 24/3, 2022 at 4:16 Comment(0)
S
-2

Faced the same issue while dealing with showModelBottomSheet, since it happens to work in a different (context)widget tree I had to level up my state to that of the app so that I could access my provider using the context.

Code snippet

Sarraute answered 28/10, 2021 at 11:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.