0
votes

I want to obtain a value from database in the initState method. So I try to access the value by calling a function using Provider.of(context) method. However, I am getting the following error:

 E/flutter ( 6424): [ERROR:flutter/lib/ui/ui_dart_state.cc(199)] Unhandled Exception: Error: Could not find the correct Provider<RestaurantTimings> above this MyApp Widget
E/flutter ( 6424):
E/flutter ( 6424): This likely happens because you used a `BuildContext` that does not include the provider
E/flutter ( 6424): of your choice. There are a few common scenarios:
E/flutter ( 6424):
E/flutter ( 6424): - The provider you are trying to read is in a different route.
E/flutter ( 6424):
E/flutter ( 6424):   Providers are "scoped". So if you insert of provider inside a route, then
E/flutter ( 6424):   other routes will not be able to access that provider.
E/flutter ( 6424):
E/flutter ( 6424): - You used a `BuildContext` that is an ancestor of the provider you are trying to read.
E/flutter ( 6424):
E/flutter ( 6424):   Make sure that MyApp is under your MultiProvider/Provider<RestaurantTimings>.
E/flutter ( 6424):   This usually happen when you are creating a provider and trying to read it immediately.
E/flutter ( 6424):
E/flutter ( 6424):   For example, instead of:
E/flutter ( 6424):   For example, instead of:
E/flutter ( 6424):
E/flutter ( 6424):   ```
E/flutter ( 6424):   Widget build(BuildContext context) {
E/flutter ( 6424):     return Provider<Example>(
E/flutter ( 6424):       create: (_) => Example(),
E/flutter ( 6424):       // Will throw a ProviderNotFoundError, because `context` is associated
E/flutter ( 6424):       // to the widget that is the parent of `Provider<Example>`
E/flutter ( 6424):       child: Text(context.watch<Example>()),
E/flutter ( 6424):     ),
E/flutter ( 6424):   }
E/flutter ( 6424):   ```
E/flutter ( 6424):
E/flutter ( 6424):   consider using `builder` like so:
E/flutter ( 6424):
E/flutter ( 6424):   ```
E/flutter ( 6424):   Widget build(BuildContext context) {
E/flutter ( 6424):     return Provider<Example>(
E/flutter ( 6424):       create: (_) => Example(),
E/flutter ( 6424):       // we use `builder` to obtain a new `BuildContext` that has access to the provider
E/flutter ( 6424):       builder: (context) {
E/flutter ( 6424):         // No longer throws
E/flutter ( 6424):         return Text(context.watch<Example>()),
E/flutter ( 6424):       }
E/flutter ( 6424):     ),
E/flutter ( 6424):   }

Here's my code:

Future<void> main() async { 
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  var _isLoading = false;
  var isOpen = false; 
  ClosingOpeningHours _timing;
  
  void initState(){ 
    super.initState();
    _getTimingOrClosing();
  }
  
  _getTimingOrClosing() async{ 
    _timing =  await Provider.of<RestaurantTimings>(context, listen: false)
                                   .fetchTiming();
    print('Start close in main ' + _timing.startClose); 
  }
   Future<void> _initPackageInfo() async {
           setState(() {
  _isLoading = true;
  });
    print('Inside initPackageInfo'); 
    final info = await PackageInfo.fromPlatform();
    setState(() {
      _packageInfo = info;
    });
  }
 
   @override
  Widget build(BuildContext context) {
   
    return !(_isLoading)?MultiProvider(
      providers: [
       ChangeNotifierProvider(
          create: (_) => Auth(),
        ),
        ChangeNotifierProxyProvider<Auth, Products>(
          create:null,
          update:(ctx,auth, previousProducts) => Products(auth.token, 
          
        auth.userId)), 
         
        ChangeNotifierProvider(
          create: (_) => Cart(),
        ),
        
        ChangeNotifierProvider(
          create: (_) => ColorChanger(),
        ),
          ChangeNotifierProxyProvider<Auth,RestaurantTimings>(
          create:null,
         update:(ctx,auth, previousTimings) => RestaurantTimings(auth.token)),
    ],

    //The consumer ensures that the material app gets built whenever Auth object changes
        child: Consumer<Auth>(builder: (ctx, auth, _) => 
          MaterialApp( 
        navigatorKey: navigatorKey,    
        title: 'MyApp',
        theme: ThemeData(
           textTheme: Theme.of(context).textTheme.apply(
            bodyColor: Colors.black,
            displayColor: Colors.black,
            ),
          primaryColor:  Colors.white,
          accentColor:  Color(0xFFF2AD18),
          fontFamily: 'Muli',
        ),
        home:      
             auth.isAuth && !isopen ? StoreTimings(): auth.isAuth && isopen?
        CategoriesScreen()
         : FutureBuilder(
                      future: auth.tryAutoLogin(),
                      builder: (ctx, authResultSnapshot) =>
                          authResultSnapshot.connectionState ==
                                  ConnectionState.waiting
                              ?  Center(
                                    child: Loading(),
                                )
                              : LoginPage(),
        ),
        routes: {
          ProductDetailScreen.routeName:(ctx) => ProductDetailScreen(),
        }
      ),
        ) 
    ):Center(child:Loading());
  }
}

The specific code which throws this error is:

_timing =  await Provider.of<RestaurantTimings>(context, listen: false)
                                   .fetchTiming();

If you look at my code, you will see that I have already listed RestaurantTiming in the multiprovider. I really need to run this before the main UI builds.

2
I need the authtoken to be able to fetch data - mcfred

2 Answers

0
votes

I don't sure it is work, but would you mind to try like this

  @override
  void initState() {
    super.initState();
    SchedulerBinding.instance.addPostFrameCallback((_) async {
      _getTimingOrClosing();
    });
  }
0
votes

I think the issue here is that context is not available during initState, as the Widget tree has not been built yet (and hence there is no context).

A workaround is to do the same job in didChangeDependencies, which runs after initState and has access to context.

To avoid fetching your data every time didChangeDependencies runs, you can create a private class property called _isInit set initially to true. Once didChangeDependencies runs for the first time it will be set to false, and our fetching code won't run again.

Here is the code:

var _isInit = true;
  @override
  void didChangeDependencies() {
    if (_isInit) {
      _getTimingOrClosing();
    }
    _isInit = false;
    super.didChangeDependencies();
  }

EDIT: After more careful inspection, I see the issue in your code. You are calling Provider in initState BUT your MultiProvider is in your Widget build. As initState runs before Build method, there is no Provider "above" the place you're calling it from. I would suggest moving your main code into another Widget which has MultiProvider as an ancestor.

Pseudo-code that you can follow and fine-tune to your requirements:

Future<void> main () async{
runApp(MyHomeApp());
}

class MyHomeApp extends StatelessWidget{
    @override
    Widget build(){
      return MultiProvider(
        providers:[....],//Your providers here
        child: MyApp(),
      );
    }
}

For MyApp Widget just remove the MultiProvider and keep everything else the same.


class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  var _isLoading = false;
  var isOpen = false; 
  ClosingOpeningHours? _timing;
  
  void initState(){ 
    super.initState();
    _getTimingOrClosing();
  }
  
  _getTimingOrClosing() async{ 
    _timing =  await Provider.of<RestaurantTimings>(context, listen: false)
                                   .fetchTiming();
    print('Start close in main ' + _timing.startClose); 
  }
  
  
   Future<void> _initPackageInfo() async {
           setState(() {
  _isLoading = true;
  });
    print('Inside initPackageInfo'); 
    final info = await PackageInfo.fromPlatform();
    setState(() {
      _packageInfo = info;
    });
  }
 
   @override
  Widget build(BuildContext context) {
   
    return !(_isLoading)?Consumer<Auth>(builder: (ctx, auth, _) => 
      MaterialApp( 
    navigatorKey: navigatorKey,    
    title: 'MyApp',
    theme: ThemeData(
       textTheme: Theme.of(context).textTheme.apply(
        bodyColor: Colors.black,
        displayColor: Colors.black,
        ),
      primaryColor:  Colors.white,
      accentColor:  Color(0xFFF2AD18),
      fontFamily: 'Muli',
    ),
    home:      
         auth.isAuth && !isopen ? StoreTimings(): auth.isAuth && isopen?
    CategoriesScreen()
     : FutureBuilder(
                  future: auth.tryAutoLogin(),
                  builder: (ctx, authResultSnapshot) =>
                      authResultSnapshot.connectionState ==
                              ConnectionState.waiting
                          ?  Center(
                                child: Loading(),
                            )
                          : LoginPage(),
    ),
    routes: {
      ProductDetailScreen.routeName:(ctx) => ProductDetailScreen(),
    }
      ),
    ):Center(child:Loading());
  }
}

Let me know if this works.