user9874313
user9874313

Reputation:

Flutter - Could not find the correct Provider

I've got an app having file structure like this: main -> auth -> home -> secret. Key codes are as below:

For main.dart:

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        StreamProvider<User>.value(value: AuthService().user),
        ChangeNotifierProvider(create: (context) => SecretProvider()),
      ],
      child: MaterialApp(
        title: 'My Secrets',
        home: AuthScreen(),
      ),
    );
  }
}

For home.dart:

class HomeScreen extends StatelessWidget {
  final AuthService _auth = AuthService();

  @override
  Widget build(BuildContext context) {
    var secretProvider = Provider.of<SecretProvider>(context);

    return ChangeNotifierProvider(
      create: (context) => SecretProvider(),
      child: Scaffold(
        appBar: AppBar(
          // some codes...
        ),
        body: StreamBuilder<List<Secret>>(
          stream: secretProvider.secrets,
          builder: (context, snapshot) {
            return Padding(
              padding: EdgeInsets.symmetric(vertical: 15.0),
              child: ListView.separated(
                // return 0 if snapshot.data is null
                itemCount: snapshot.data?.length ?? 0,
                itemBuilder: (context, index) {
                  return ListTile(
                    leading: Icon(Icons.web),
                    title: Text(snapshot.data[index].title),
                    trailing: Icon(Icons.edit),
                    onTap: () {
                      Navigator.push(
                        context,
                        MaterialPageRoute(
                          builder: (context) => SecretScreen(
                            secret: snapshot.data[index],
                          ),
                        ),
                      );
                    },
                  );
                },
                separatorBuilder: (context, index) {
                  return Divider();
                },
              ),
            );
          },
        ),
        floatingActionButton: FloatingActionButton(
          child: Icon(Icons.add),
          onPressed: () {
            Navigator.push(
              context,
              MaterialPageRoute(builder: (context) => SecretScreen()),
            );
          },
        ),
      ),
    );
  }
}

For secret.dart:

class SecretScreen extends StatefulWidget {
  final Secret secret;

  SecretScreen({this.secret});

  @override
  _SecretScreenState createState() => _SecretScreenState();
}

class _SecretScreenState extends State<SecretScreen> {
  // some codes...

  @override
  void initState() {
    final secretProvider = Provider.of<SecretProvider>(context, listen: false);

    // some codes...

    super.initState();
  }

  @override
  void dispose() {
    // some codes...

    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final secretProvider = Provider.of<SecretProvider>(context);

    return Scaffold(
      // some codes...
    );
  }
}

These codes worked just fine, but later on I decided to move the ChangeNotifierProvider from main.dart to home.dart due to some class instance life cycle issue. The new code is like below:

For main.dart:

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        StreamProvider<User>.value(value: AuthService().user),
      ],
      child: MaterialApp(
        title: 'My Secrets',
        home: AuthScreen(),
      ),
    );
  }
}

For home.dart:

class HomeScreen extends StatelessWidget {
  final AuthService _auth = AuthService();

  @override
  Widget build(BuildContext context) {
    // var secretProvider = Provider.of<SecretProvider>(context);

    return ChangeNotifierProvider(
      create: (context) => SecretProvider(),
      child: Consumer<SecretProvider>(
        builder: (context, secretProvider, child) {
          return Scaffold(
            appBar: AppBar(
              // some codes...
            ),
            body: StreamBuilder<List<Secret>>(
              stream: secretProvider.secrets,
              // stream: SecretProvider().secrets,
              builder: (context, snapshot) {
                return Padding(
                  padding: EdgeInsets.symmetric(vertical: 15.0),
                  child: ListView.separated(
                    // return 0 if snapshot.data is null
                    itemCount: snapshot.data?.length ?? 0,
                    itemBuilder: (context, index) {
                      return ListTile(
                        leading: Icon(Icons.web),
                        title: Text(snapshot.data[index].title),
                        trailing: Icon(Icons.edit),
                        onTap: () {
                          Navigator.push(
                            context,
                            MaterialPageRoute(
                              builder: (context) => SecretScreen(
                                secret: snapshot.data[index],
                              ),
                            ),
                          );
                        },
                      );
                    },
                    separatorBuilder: (context, index) {
                      return Divider();
                    },
                  ),
                );
              },
            ),
            floatingActionButton: FloatingActionButton(
              child: Icon(Icons.add),
              onPressed: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (context) => SecretScreen()),
                );
              },
            ),
          );
        },
      ),
    );
  }
}

Basically, I just moved the ChangeNotifierProvider to home.dart and used a Consumer to pass the context, but this time, whenever I navigate to secret screen, it prompts me error like below:

Could not find the correct Provider<SecretProvider> above this SecretScreen Widget

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

This BuildContext is really bugging me. Even if I'm having ChangeNotifierProvider one level lower than before, the SecretScreen widget should still be aware of the SecretProvider that passed on from HomeScreen because it's still the child of HomeScreen and according to my knowledge, the context should contain the SecretProvider.

Upvotes: 1

Views: 101

Answers (1)

F Perroch
F Perroch

Reputation: 2215

You get this error because your SecretProvider instance is part of HomeScreen which is not a parent of SecretScreen.

In order, when you push a new page, this new page is not a descendent of the previous one so you can't access to inherited object with the .of(context) method.

Here the a schema representing the widget tree to explain the situation :

  1. With a Provider on top of MaterialApp (the navigator) :

    Provider
      MaterialApp
        HomeScreen -> push SecretScreen
        SecretScreen -> Here we can acces the Provider by calling Provider.of(context) because the context can access to its ancestors 
    
  2. With a Provider created in HomeScreen :

    MaterialApp
      HomeScreen -> push SecretScreen
        Provider -> The provider is part of HomeScreen
      SecretScreen -> The context can't access to the Provider because it's not part of its ancestors
    

I hope my answer is pretty clear and will help you to understand what happens ;)

Upvotes: 2

Related Questions