Provider: Provider.of<Data>(context) not working for AppBar Title

Created on 17 Mar 2020  Β·  9Comments  Β·  Source: rrousselGit/provider

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(MyApp());
}

class Data extends ChangeNotifier {
  String texto = 'Mi secreto';
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<Data>(
      create: (context) => Data(),
      child: MaterialApp(
        home: Scaffold(
          appBar: AppBar(
            title: Text(Provider.of<Data>(context).texto),
          ),
          body: Level1(),
        ),
      ),
    );
  }
}

class Level1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Level2();
  }
}

class Level2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Text(Provider.of<Data>(context).texto),
        Level3(),
      ],
    );
  }
}

class Level3 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      alignment: Alignment.topCenter,
      child: Text(
        'h',
        textAlign: TextAlign.center,
      ),
    );
  }
}

This is the error I get:

════════ Exception caught by widgets library ═══════════════════════════════════════════════════════
The following ProviderNotFoundException was thrown building MyApp(dirty):
Error: Could not find the correct Provider<Data> above this MyApp Widget

To fix, please:

  * Ensure the Provider<Data> is an ancestor to this MyApp Widget
  * Provide types to Provider<Data>
  * Provide types to Consumer<Data>
  * Provide types to Provider.of<Data>()
  * Ensure the correct `context` is being used.

If none of these solutions work, please file a bug at:
https://github.com/rrousselGit/provider/issues

As long as I switch "Provider.of(context).texto" for a dumby text e.g. 'dumby text', everything works fine it even shows the "Provider.of(context).texto" well in Level 2.

I'm using 4.0.4 version

All 9 comments

I just learned why it happens. It's because ChangeNotifierProvider cannot have as a child MaterialApp directly but Consumer as following:

ChangeNotifierProvider<Data>(
      create: (context) => Data(),
      child: Consumer<Data>(
        builder: (context, provider, child) => MaterialApp(

So, it cannot be (like I originally had):

child: MaterialApp()

I know it used to work simpler (just with the child: MaterialApp()) in the past but not any more. I don't understand why. Can anyone explain that?
Thanks,

The solution I provided above has one downside.listen: false is not functional anymore. If you write this in any of the sections, it will still listen:

Provider.of<Data>(context, listen: false).texto

Any ideas on how to fix that?

Here is the full code:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<Data>(
        create: (context) => Data(),
        child: Consumer<Data>(
          builder: (context, provider, child) => MaterialApp(
            home: Scaffold(
              appBar: AppBar(
                title: Text(Provider.of<Data>(context, listen: false).texto),
              ),
              body: Level1(),
            ),
          ),
        ));
  }
}

class Level1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Level2();
  }
}

class Level2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        TextField(
          onChanged: (newValue) {
            Provider.of<Data>(context, listen: false).changeString(newValue);
          },
        ),
        Level3(),
      ],
    );
  }
}

class Level3 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      alignment: Alignment.topCenter,
      child: Text(
        Provider.of<Data>(context, listen: false).texto,
        textAlign: TextAlign.center,
      ),
    );
  }
}


class Data extends ChangeNotifier {
  String texto = 'Mi secreto';

  void changeString(String newValue) {
    texto = newValue;
    notifyListeners();
  }
}

I think you can make it better by doing this:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(ChangeNotifierProvider<Data>(
    create: (context) => Data(),
    child: MyApp(),
  ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return
      MaterialApp(
        home: Scaffold(
          appBar: AppBar(
            title: Selector<Data, String>(
              selector: (_, provider) => provider.texto,
              builder: (context, texto, child) => Text(texto),
            ),
          ),
          body: Level1(),
        ),
      );
  }
}

and updated Level3 to use Selector also.

Great, thanks @medyas. It looks like the main function is enough to solve the original issue. So here is the full working code. Answer to my original question:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(ChangeNotifierProvider<Data>(
    create: (context) => Data(),
    child: MyApp(),
  ));
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text(Provider.of<Data>(context, listen: true).texto),
        ),
        body: Level1(),
      ),
    );
  }
}

class Level1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Level2();
  }
}

class Level2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        TextField(
          onChanged: (newValue) {
            Provider.of<Data>(context, listen: false).changeString(newValue);
          },
        ),
        Level3(),
      ],
    );
  }
}

class Level3 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      alignment: Alignment.topCenter,
      child: Text(
        Provider.of<Data>(context, listen: false).texto,
        textAlign: TextAlign.center,
      ),
    );
  }
}

class Data extends ChangeNotifier {
  String texto = 'Mi secreto';

  void changeString(String newValue) {
    texto = newValue;
    notifyListeners();
  }
}

From what it looks like, it doesn't even matter whether the state that I pass goes above/below in THE WIDGET TREE but what matters whether it goes above/below THE CLASS (in a different class). When I had
ChangeNotifierProvider in the widget tree above BUT still within the same classβ€”it didn't work. As soon as I moved it up the class (in the main function), it suddenly worked.

Closing as it seems to be fixed

@rrousselGit but you still haven't answered a few concerning questions:

  • Why can't we wrap MaterialApp widget with Provider? In other words, why doesn't it work when we do that? see more comments above
  • Shouldn't the provider be above in the widget tree for the wrapper to work correctly? Then why is it that in my examples it's not the case but rather Provider needs to above THE CLASS and not above the widget tree? Again, just read the comments above.

If you don't answer, it's hard to know whether it's intentional and if it isβ€”why? Or it is just rather a bug.

Why can't we wrap MaterialApp widget with Provider? In other words, why doesn't it work when we do that? see more comments above

You can. The problem is you used the wrong BuildContext: the context of MyApp, which is an ancestor of the provider and therefore do not have access to the provider

Thanks @rrousselGit for your answer. So, then I guess the only solution is to wrap MyApp then like this:

void main() {
  runApp(ChangeNotifierProvider<Data>(
    create: (context) => Data(),
    child: MyApp(),
  ),
  );
}

Or is there any other way (a better way) to wrap the MaterialApp Widget for it to work with a different context?

What's good

Or you can use the Builder widget

Was this page helpful?
0 / 5 - 0 ratings