Offstage Navigators rebuild - flutter

I have implemented the multiple Offstage Navigators for the bottomNavigationBar, further details can be seen here. The problem is that when using this method, each time we select a bottom navigation item, the FutureBuilder runs the future method and rebuilds the entire widget, each Offstage widget and all their children are also rebuilt.
For each Offstage widget, I'm loading data via html request and that means each time I switch a tab, 5 requests will be made.
This is my main Scaffold which holds the bottomNavigationBar.
#override
Widget build(BuildContext context) {
TabProvider tabProvider = Provider.of<TabProvider>(context);
return FutureBuilder(
future: initProvider(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return WillPopScope(
onWillPop: _onWillPop,
child: Scaffold(
appBar: AppBar(
title: Text(tabName[tabProvider.currentTab],
),
body: Stack(children: <Widget>[
_buildOffstageNavigator(TabItem.feed, tabProvider.currentTab),
_buildOffstageNavigator(TabItem.explore, tabProvider.currentTab),
_buildOffstageNavigator(TabItem.guide, tabProvider.currentTab),
_buildOffstageNavigator(TabItem.map, tabProvider.currentTab),
_buildOffstageNavigator(TabItem.profile, tabProvider.currentTab),
]),
bottomNavigationBar: BottomNavigation(
currentTab: tabProvider.currentTab,
onSelectTab: tabProvider.selectTab,
),
),
);
} else {
return Text('Loading');
}
},
);
}
The FutureBuilder will initialize the values in my provider so each tab can access the cached data.
The _buildOffstageNavigator will return the below
return Offstage(
offstage: currentTab != tabItem,
child: TabNavigator(
navigatorKey: navigatorKeys[tabItem],
tabItem: tabItem,
),
);
Below is the Widget which is built inside the Scaffold body and hence inside the Offstage Navigator from above.
#override
Widget build(BuildContext context) {
TabProvider tabProvider = Provider.of<TabProvider>(context);
States stateData = tabProvider.exploreStateCache;
return Container(
child: ListView(
children: <Widget>[
Text(stateData.stateName),
Text(stateData.stateDescription),
],
),
);
}
I have followed this articles advice for using futures with the provider but something else is missing

Instead of creating futures in a build method, which as you have noticed, may be called several times, create them in a place that is invoked only once. For example, a StatefulWidget's initState:
class Foo extends StatefulWidget {
#override
_FooState createState() => _FooState();
}
class _FooState extends State<Foo> {
Future<MyData> _dataFuture;
#override
void initState() {
super.initState();
_dataFuture = getData();
}
#override
Widget build(BuildContext context) => FutureBuilder<MyData>(
future: _dataFuture,
builder: (context, snapshot) => ...,
);
}
A second thing you can improve is reduce the scope of what gets rebuild when the provider provides a new value for TabProvider. The context that you call Provider.of<Data>(context) gets rebuild when there's a new value for Data. That is done most conveniently with the various other widgets offered by the provider package, like Consumer and Selector.
So remove the Provider<TabProvder>.of(context) calls and use Consumers and Selectors. For example, to only rebuild the title when a tab is switched:
AppBar(
title: Selector<TabProvider, String>(
selector: (context, tabProvider) => tabName[tabProvider.currentTab],
builder: (context, title) => Text(title),
),
)
Selector only rebuilds the Text(title) widget, when the result of its selector callback is different from the previous value. Similarly for _buildOffstageNavigator:
Widget _buildOffstageNavigator(BuildContext context, TabItem tabItem) {
return Selector<TabProvider, bool>(
selector: (context, tabProvider) => tabProvider.currentTab != tabItem,
builder: (context, isCurrent) => Offstage(
offstage: isCurrent,
child: Selector<TabProvider, Key>(
selector: (context, tabProvider) => tabProvider.navigatorKeys[tabItem],
builder: (context, tabKey) => TabNavigator(
navigatorKey: tabKey,
tabItem: tabItem,
),
),
);
}
(Beware: All code is untested and contains typos)

Related

Flutter FutureBuilder resolving multiple times without build() being called

I'm having an issue with my widget running its FutureBuilder code multiple times with an already resolved Future. Unlike the other questions on SO about this, my build() method isn't being called multiple times.
My future is being called outside of build() in initState() - it's also wrapped in an AsyncMemoizer.
Relevant code:
class _HomeScreenState extends State<HomeScreen> {
late final Future myFuture;
final AsyncMemoizer _memoizer = AsyncMemoizer();
#override
void initState() {
super.initState();
/// provider package
final homeService = context.read<HomeService>();
myFuture = _memoizer.runOnce(homeService.getMyData);
}
#override
Widget build(BuildContext context) {
print("[HOME] BUILDING OUR HOME SCREEN");
return FutureBuilder(
future: myFuture,
builder: ((context, snapshot) {
print("[HOME] BUILDER CALLED WITH SNAPSHOT: $snapshot - connection state: ${snapshot.connectionState}");
When I run the code, and trigger the bug (a soft keyboard being shown manages to trigger it 50% of the time, but not all the time), my logs are:
I/flutter (29283): [HOME] BUILDING OUR HOME SCREEN
I/flutter (29283): [HOME] BUILDER CALLED WITH SNAPSHOT: AsyncSnapshot<dynamic>(ConnectionState.waiting, null, null, null) - connection state: ConnectionState.waiting
I/flutter (29283): [HOME] BUILDER CALLED WITH SNAPSHOT: AsyncSnapshot<dynamic>(ConnectionState.done, Instance of 'HomeData', null, null) - connection state: ConnectionState.done
...
/// bug triggered
...
I/flutter (29283): [HOME] BUILDER CALLED WITH SNAPSHOT: AsyncSnapshot<dynamic>(ConnectionState.done, Instance of 'HomeData', null, null) - connection state: ConnectionState.done
The initial call with ConnectionState.waiting is normal, then we get the first build with ConnectionState.done.
After the bug is triggered, I end up with another FutureBuilder resolve without the build() method being called.
Am I missing something here?
Edit with full example
This shows the bug in question - if you click in and out of the TextField, the FutureBuilder is called again.
It seems related to how the keyboard is hidden. If I use the FocusScopeNode method, it will rebuild, whereas if I use FocusManager, it won't, so I'm not sure if this is a bug or not.
import 'package:flutter/material.dart';
void main() async {
runApp(const TestApp());
}
class TestApp extends StatelessWidget {
const TestApp({super.key});
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Testapp',
home: Scaffold(
body: TestAppHomeScreen(),
),
);
}
}
class TestAppHomeScreen extends StatefulWidget {
const TestAppHomeScreen({super.key});
#override
State<TestAppHomeScreen> createState() => _TestAppHomeScreenState();
}
class _TestAppHomeScreenState extends State<TestAppHomeScreen> {
late final Future myFuture;
#override
void initState() {
super.initState();
myFuture = Future.delayed(const Duration(milliseconds: 500), () => true);
print("[HOME] HOME SCREEN INIT STATE CALLED: $hashCode");
}
#override
Widget build(BuildContext context) {
print("[HOME] HOME SCREEN BUILD CALLED: $hashCode");
return FutureBuilder(
future: myFuture,
builder: (context, snapshot) {
print("[HOME] HOME SCREEN FUTURE BUILDER CALLED WITH STATE ${snapshot.connectionState}: $hashCode");
if (snapshot.connectionState == ConnectionState.waiting) {
return const Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
return GestureDetector(
onTapUp: (details) {
// hide the keyboard if it's showing
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus();
}
// FocusManager.instance.primaryFocus?.unfocus();
},
child: const Scaffold(
body: Center(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 32.0),
child: TextField(),
),
),
),
);
},
);
}
}
Thank you for the full, reproducible example.
print statements inside the builder method of your FutureBuilder are likely misleading you towards the incorrect "culprit".
The key "problem" arises from this line:
FocusScopeNode currentFocus = FocusScope.of(context);
In case you didn't know, Flutter's .of static methods expose InheritedWidget APIs of some kind. By convention, in a .of method you can usually find a call to dependOnInheritedWidgetOfExactType, which is meant to register the caller, i.e. the children Widget, as a dependency, i.e. a Widget that depends and react to changes of a InheritedWidget of that type.
Shortly, putting a .of inside a build method is meant to trigger rebuilds on your Widget: it's actively registered for listening to changes!
In your code, FutureBuilder's builder method is being registered as dependant of FocusScope.of and will be rebuilt if FocusScope changes. And yes, that does happen whenever we change focus. Indeed, you can even move up those few lines (outside GestureDetector, directly in the builder scope), and you'd obtain even more rebuilds (4: one for the first focus change, then others subsequent caused by the focus shift caused by such rebuilds).
One quick fix would be to directly look for the associated InheritedWidget these API expose, and then, instead of a simple .of, you'd call:
context.getElementForInheritedWidgetOfExactType<T>();
EDIT. I just looked for T in your use case. Unluckily, it turns out it is a _FocusMarker extends InheritedWidget class, which is a private class, and therefore it cannot be used outside of its file / package. I'm not sure why they designed the API like that, but I am not familiar with FocusNodes.
An alternative approach would be to simply isolate the children for your FutureBuilder, like so:
builder: (context, snapshot) {
print("[HOME] HOME SCREEN FUTURE BUILDER CALLED WITH STATE ${snapshot.connectionState}: $hashCode");
// ...
return Something();
}
Where Something is just the refactored StatelessWidget that contains the UI you've shown there. This would rebuild just Something and not the whole builder method, if that's your concern.
You want to deepen the "how" and the "whys" of InheritedWidgets, make sure you first watch this video to correctly understand what InheritedWidgets are. Then, if you wish to understand how to exploit didChangeDependencies, watch this other video and you'll be good to go.
You need to understand the role of BuildContext.
Example-1:
I'm using context passed to the Widget.build() method, and doing
FocusScope.of(context).unfocus();
will invoke both build() and builder() method because you're telling Flutter to take the focus away from any widget within the context and therefore the Widget.build() gets called, which further calls the Builder.builder() method.
// Example-1
#override
Widget build(BuildContext context) {
print("Widget.build()");
return Builder(builder: (context2) {
print('Builder.builder()');
return GestureDetector(
onTap: () => FocusScope.of(context).unfocus(), // <-- Using `context`
child: Scaffold(
body: Center(
child: TextField(),
),
),
);
});
}
Example-2:
I'm using context2 passed to the Builder.builder() method, and doing
FocusScope.of(context2).unfocus();
will invoke only the builder() method because you're telling Flutter to take the focus away from any widget within the context2 and thus the Builder.builder() gets called.
// Example-2
#override
Widget build(BuildContext context) {
print("Widget.build()");
return Builder(builder: (context2) {
print('Builder.builder()');
return GestureDetector(
onTap: () => FocusScope.of(context2).unfocus(), // <-- Using `context2`
child: Scaffold(
body: Center(
child: TextField(),
),
),
);
});
}
To answer your question, if you replace
builder: (context, snapshot) { ...}
with
builder: (_, snapshot) { }
then your build() will also get called.
The difference was happen because the context you use is parent
context (from future builder method).
Just wrap GestureDetector with Builder then the result is same as 2nd way.
return Builder(builder: (_context) {
return GestureDetector(
onTapUp: () {
// hide the keyboard if it's showing
final currentFocus = FocusScope.of(_context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus();
},
} ...
When attempting to dismiss keyboard we should use second way FocusManager.instance.primaryFocus?.unfocus(); as discussion in official issue here:
https://github.com/flutter/flutter/issues/20227#issuecomment-512860882
https://github.com/flutter/flutter/issues/19552
Please try this solution /// provider package up super.initState();
your code will be like this
#override
void initState() {
/// provider package
final homeService = context.read<HomeService>();
myFuture = _memoizer.runOnce(homeService.getMyData);
super.initState();
}
please after trying it tell me the result
pass descendant context to FocusScope.of will not trigger the build(), i think because focus manager remove child for this parent (FutureBuilder), and reassign it based on current context, in this case build() context, so futurebuilder need to rebuild.
Widget build(BuildContext context) {
print("[HOME] HOME SCREEN BUILD CALLED: $hashCode");
return FutureBuilder(
future: myFuture,
builder: (context, snapshot) {
print("[HOME] HOME SCREEN FUTURE BUILDER CALLED WITH STATE ${snapshot.connectionState}: $hashCode");
if (snapshot.connectionState == ConnectionState.waiting) {
return const Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
//make StatefulBuilder as parent will prevent it
return StatefulBuilder(
builder: (context, setState) {
return GestureDetector(
onTapUp: (details) {
// hide the keyboard if it's showing
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus();
}
// FocusManager.instance.primaryFocus?.unfocus();
},
child: const Scaffold(
body: Center(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 32.0),
child: TextField(),
),
),
),
);
}
);
},
);
}
to prove it , i try to warp it parent (FutureBuilder) with another builder :
return LayoutBuilder(
builder: (context, box) {
print('Rebuild');
return FutureBuilder(
future: myFuture,
builder: (context, snapshot) {
print("[HOME] HOME SCREEN FUTURE BUILDER CALLED WITH STATE ${snapshot.connectionState}: $hashCode");
if (snapshot.connectionState == ConnectionState.waiting) {
return const Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
return GestureDetector(
onTapUp: (details) {
// hide the keyboard if it's showing
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus();
}
// FocusManager.instance.primaryFocus?.unfocus();
},
child: const Scaffold(
body: Center(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 32.0),
child: TextField(),
),
),
),
);
},
);
}
);
build() method not reinvoked because focusScope manager only rebuild context from FutureBuilder (Parent)

how to implement scoped model to navigation screen when scoped model initialized in the previous screen and not in myApp()

im trying to use scoped model in 2 screens in the app and i dont want to run the app with scoped model, i just initialize scoped model in first screen and it worked but i got an error in the second screen where i navigate to it. so what do i do?
first i call the first screen from the pre screen like this
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ServiceDetails(model: new CartModel()),
)
);
then in ServiceDetails i didnt get any error
so in build widget
#override
Widget build(BuildContext context) {
return ScopedModel(
model: widget.model,
child: Scaffold(...)
);
}
and i have a cart button which on tap:
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => new Cart()
),
);
},
Cart class:
class Cart extends StatefulWidget {
#override
_CartState createState() => _CartState();
}
class _CartState extends State<Cart> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Your Cart")),
body: ScopedModel.of<CartModel>(context, rebuildOnChange: true).cart.length == 0 ?
Container(
alignment: Alignment.center,
child: Text("Your cart is empty!",style: new TextStyle(color: Colors.grey))
) :
Container(
padding: EdgeInsets.only(top:15),
child: cartListView()
)
);
}
Widget cartListView(){
return ScopedModelDescendant<CartModel>(
builder: (context, child, model) {
return ListView.builder(
shrinkWrap: true,
itemCount: ScopedModel.of<CartModel>(context,rebuildOnChange: true).total,
itemBuilder: (context, index) {
return Container(
child: Image.asset(model.cart[index].fs.image)
)
}
)})}
}
so when i enter cart page i got an error
can't find the correct scoped model
anyone help plz

Rebuild the text of the outer class with Provider

The code below is a simplification of my project.
I want it make like: When you push the button, text is rebuilt and increase the number.
However, text isn't reloaded. but I detected that the number of count is actualy increased.
Why this text isn't reloaded? Some way to detect changes of other classes?
class CLS extends ChangeNotifier{
int count = 0;
void add(int i){
count += i;
notifyListeners();
}
}
....
class Main extends StatelessWidget{
Widget txt(){
return Consumer<CLS>(
builder: (context, value, _) => Text(value.count.ToString())
);
}
Widget but(int i){
return ElevatedButton(
child: Text("Button"),
onPressed: context.read<CLS>().add(i)
);
}
#override
Widget build(BuildContext context){
return Scaffold(
body: ChangeNotifierProvider(
create: (BuildContext context) => CLS(),
child: Column( children: [
txt(),
but(1)
]));
}
}
You are using same context for providing and accessing your Provider, so it can't find it. You should use new context to access your provided Provider, because it looks for it's ancessor to find provided Provider. And also you should use that new context to read your Provider from your but() method, like so:
Widget but(int i, BuildContext ctx) {
return ElevatedButton(
child: Text("Button"), onPressed: () => ctx.read<CLS>().add(i));
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ChangeNotifierProvider(
create: (BuildContext context) => CLS(),
child: Builder(builder: (ctx) { // new context(ctx) to find Provider from parent's(ancessor's) context
return Column(
children: [
txt(),
but(1, ctx), // use that new context in your but method for accessing Provider
],
);
}),
),
);
}

BlocConsumer accessing multiple providers

I have this app where my widget is wrapped with MultiBlocProvider where I send down 2 providers:
MultiBlocProvider(
providers: [
BlocProvider<AppCubit>(
create: (BuildContext context) => AppCubit(),
),
BlocProvider<ProfileCubit>(
create: (BuildContext context) => ProfileCubit(),
),
],
child: HomePage(),
On the HomePage widget I access the first one using a BlocConsumer but I don't know how to grab all of them. Do I need to keep nesting BlocConsumer into the builder section in order to access my providers? What is the recommended way of accessing the x number of providers I send down to my widgets?
class HomePage extends StatefulWidget {
#override
Widget build(BuildContext context) {
return BlocConsumer<AppCubit, AppState>(
...
builder: (context, state) {
return Scaffold(
backgroundColor: Theme.of(context).primaryColorLight,
body: null,
);
},
...
);
}
}
The MultiBlocProvider is adding your Blocs to the context down the BuildTree to the children of MultiBlocProvider, hence to the HomePage().
BlocConsumer is analog to using a BlocBuilder(for Rebuilding UI after State Change) and a BlocListener(for other reactions like navigation after State Change).
You can assign your Blocs in the initState() like following:
#override
void initState() {
super.initState();
appCubit = BlocProvider.of<AppCubit>(context);
profileCubit = BlocProvider.of<ProfileCubit>(context);
appCubit.add(SomeFetchEvent());
profileCubit.add(SomeFetchEvent());
}
NOTE: in the BlocConsumer/BlocBuilder you want to show UI regarding the current state. Therefore you must decide in during which State you want to nest the next BlocConsumer/BlocBuilder. For example:
BlocConsumer<AppCubit, AppState>(
...
builder: (context, state) {
if (state == *someState*){
// Nest next Consumer
BlocConsumer<ProfileCubit, AppState>(
...
builder: (context, state) {
if(state == *someState*){ return ...}
},
...
);
}
},
...
);
You might see, that it is not really useful to do that. If you don't need to change your UI if a State changes in AppCubit it would be useful to consider putting it in a BlocListener and put Profile Cubit in a BlocConsumer/BlocBuilder. For example:
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).primaryColorLight,
body: BlocListener<AppCubit, AppState>(
listener: (context, state) {
// do some stuff here, like Navigating, Changing Variable at specific
// state
},
child: BlocBuilder<ProfileCubit, ProfileState>(
builder: (context, state){
// Change your UI according to the current state
if(state == *someState*){
return *someWidget*
}
}
)
);
}
You will find more details here:
https://bloclibrary.dev/#/flutterbloccoreconcepts?id=multiblocprovider

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

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://stackoverflow.com/a/56533611/2457045 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://stackoverflow.com/a/56533611/2457045 solution to Modal Bottom Sheet?
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());
},
);
}
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),
);
},
);
}
You need move Provider to top layer(MaterialApp)
According to picture, Dialog widget is under MaterialApp, so this is why you using wrong context
wrap your whole child widget inside the consumer.
void myShowDialog() {
showDialog(
context: context,
builder: Consumer<MyModel>(
builder: (context, value, builder) {
retuen widget();
}
);
}
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),
),
);
}
}
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);}),);
}
}
);
}
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.
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(),
),
);
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.