10

I have a problem with Flutter Provider pattern. After user is redirected to a new screen, the provider could not be found.

Following my previous question (Could not find the correct provider above this widget) I wrote this code:

class NewRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final title = 'Tap to select';
    return MaterialApp(
        title: title,
        home: Scaffold(
          appBar: AppBar(
            title: Text(title),
          ),
          body: NewRouteBody()
        ));
  }
}

class NewRouteBody extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var user = Provider.of<UserRepository>(context);
    return ListView(...)

I did same thing but I get again the error which says that it could not find the correct provider above this widget (NewRouteBody).

Tried to fix it somehow, Googled the answer for a few hours but without success...

Any help is appreciated.

EDIT

This is UserRepository which contains pattern:

class UserRepository with ChangeNotifier {
  User user;
  Status _status = Status.Uninitialized;

  Status get status => _status;
  User get getUser => user;
...}

EDIT 2:

Code snippet with ChangeNotifier:

void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.red,
      ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<UserRepository>(
      builder: (context) => UserRepository.instance(),
      child: Consumer<UserRepository>(
        builder: (context, UserRepository userRepository, _) {
          switch (userRepository.status) {
            case Status.Uninitialized:
              return Login();
            case Status.Unauthenticated:
              return Login();
            case Status.Authenticating:
            case Status.Authenticated:

              if(userRepository.getUser.isPrefSet == 0){
                return Selection();
              }
              return Dashboard();
          }
        },
      ),
    );
  }
}
harunB10
  • 4,823
  • 15
  • 63
  • 107

3 Answers3

19

The issue is:

Your ChangeNotifierProvider is located inside Home, but you are trying to access it outside Home.

Providers are scoped. Which means that if it's located inside a widget tree, only its descendants can access it. As such, in your code, only Home can read from the provider.

To fix that, move the provider above MaterialApp:

ChangeNotifierProvider<UserRepository> (
  builder: (context) => UserRepository(),
  child: MaterialApp(
    home: Home(),
  ),
)
Rémi Rousselet
  • 256,336
  • 79
  • 519
  • 432
  • Thx a lot. Have to test it though. Like I said, my goal is to have some kind of user session where all Widgets could have access to (updated) `UserRepository` members... Provider seems like great solution, but I didn't know it is scoped. – harunB10 Jul 29 '19 at 11:39
  • 2
    If the provider is above `MaterialApp`, then it's similar to a global variable. – Rémi Rousselet Jul 29 '19 at 11:40
  • Great! Sorry, one more question... Do all provider types (ChangeNotifier, Provider, etc..) rebuild all listening Widgets? I am thinking about performance because my app will have a lot of screens (widgets). I only need variable as a global. Is `ChangeNotifier` good option for this? – harunB10 Jul 29 '19 at 11:44
  • 1
    Only the widgets that depends on the "global" will update when that value changes. So no worries – Rémi Rousselet Jul 29 '19 at 11:48
2

You first need to create the Provider and place in the tree above the usage. for example, in your case:

  Widget build(BuildContext context) {
final title = 'Tap to select';
return MaterialApp(
    title: title,
    home: Scaffold(
        appBar: AppBar(
          title: Text(title),
        ),
        body: Provider<UserRepository> (
          builder: (context) => UserRepository(),
            dispose: (context, val) => val.dispose(),
            child: NewRouteBody())
    ));

}

MozesM
  • 437
  • 3
  • 8
  • I edited my question. After login I want to set user preferences. If I create new Provider, then it will reinitialize `UserRepository` and it will be `null` if I am not mistaken? – harunB10 Jul 28 '19 at 22:25
0

When the application reports such an error, it can be from many reasons. In my case, I was trying to read data from a context that was not wrapped by the BlocProvider from its ancestor.

// In my Child Widget
Navigator.push(context, MaterialPageRoute(
   builder: (_) => MultiBlocProvider(providers: [
                  BlocProvider.value(
                      value: SaveJobsCubit()),
                  BlocProvider.value(
                      value: context.read<OnlineCompaniesCubit>()),
                  BlocProvider.value(
                      value: context.read<ApplyJobsCubit>()),
                  ], 
               child: AttractiveJobsScreen(),
             )
// But in Parent Widget, I create MultiBlocProvider with case have access_token
AuthRepo.accessToken != null
    ? RepositoryProvider(
        create: (context) => OnlineCompaniesRepo(),
        child: MultiBlocProvider(
          providers: [
            BlocProvider(
              create: (context) => SaveJobsCubit(),
            ),
            BlocProvider(
              create: (context) => OnlineCompaniesCubit(context.read<OnlineCompaniesRepo>()),
            ),
            BlocProvider(
              lazy: false,
              create: (context) => ApplyJobsCubit(),
            ),
          ],
          child: current,
        ),
      )
    : RepositoryProvider(
        create: (context) => OnlineCompaniesRepo(),
        child: BlocProvider(
          create: (context) => OnlineCompaniesCubit(context.read<OnlineCompaniesRepo>()),
          child: current,
        ),
      );

This causes an error in case there is no access_token, then the child screen will not have SaveJobsCubit and cause the above error.

Hope this helps someone.

Doan Bui
  • 3,572
  • 25
  • 36