10

The provider values are coming from the parent widget. I can use the provider values under the build context. However I need the provider values in the getHomeCampaigns function. I tried to define local variables and assign them to the provider values once the widget is built, but the function claims that the variables are called on null. I guess they are being used before they are being set under the build context from the provider.

class HomeScreen extends StatefulWidget {
  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  // Variables to be set inside the function and then used by the last widget
  User currentUser;
  String country;
  List<CampaignWidget> friendsCamps;
  List<CampaignWidget> featuredCamps;

  // Function that needs the provider values
  getHomeCampaigns() async {
    // Get all campaigns where their owner is in the list of friends for the current user
    QuerySnapshot friendsSnapshot = await Firestore.instance
        .collection('campaigns')
        .where('user.uid', whereIn: currentUser.friends)
        .where('attributes.active', isEqualTo: true)
        .orderBy('created', descending: true)
        .limit(20)
        .getDocuments();
    // Get featured campaigns documents in the current country
    QuerySnapshot featuredSnapshot = await Firestore.instance
        .collection('campaigns')
        .where('attributes.featured', isEqualTo: true)
        .where('funders.${currentUser.uid}', isEqualTo: false)
        .where('country', isEqualTo: country)
        .orderBy('user.rating', descending: true)
        .limit(5)
        .getDocuments();
    // Make 2 lists of CampaignWidget out of the documents retrieved
    List<CampaignWidget> campaigns = friendsSnapshot.documents
        .map((doc) => CampaignWidget(campaign: Campaign.fromDocument(doc)))
        .toList();
    List<CampaignWidget> featured = featuredSnapshot.documents
        .map((doc) => CampaignWidget(campaign: Campaign.fromDocument(doc)))
        .toList();
    setState(() {
      // Set the featured and friends lists of CampaignWidget to the newly made lists
      this.featuredCamps = featured;
      this.friendsCamps = campaigns;
    });
  }

  @override
  void initState() {
    super.initState();
    getHomeCampaigns();
  }

  @override
  Widget build(BuildContext context) {
    this.currentUser = Provider.of<User>(context);
    this.country = Provider.of<String>(context);
    return Scaffold(
      backgroundColor: Color(0xFFE8E8E8),
      appBar: AppBar(
        centerTitle: false,
        title: Text("Home"),
        actions: <Widget>[
          /// Search users
          IconButton(
            icon: Icon(
              Icons.search,
            ),
            onPressed: () {},
          ),
        ],
      ),
      body: RefreshIndicator(
        onRefresh: () => getHomeCampaigns(),
        child: // Some widget that uses the variables,
      ),
    );
  }
}
Omar Ellakany
  • 113
  • 1
  • 1
  • 7

3 Answers3

13

You can read the variables from your provider like this: Provider.of(context, listen: false).yourVariable in getHomeCampaigns. You must have listen: false for this to work though because it is not in the widget tree so it can't update whenever yourVariable changes -- see . If this doesn't work there is a problem where you declare you provider.

MendelG
  • 14,885
  • 4
  • 25
  • 52
Gabe
  • 5,643
  • 3
  • 26
  • 54
  • 1
    can we access outside the context of provider on which it is initialised – akash maurya Aug 10 '20 at 16:31
  • @akashmaurya no, their must be a provider above your context in order to use it. – Gabe Aug 10 '20 at 17:26
  • or we have to create new provider with a value like ChangeNotifierProvider.value() – akash maurya Aug 18 '20 at 12:49
  • 1
    It is not recommended to create a new provider under Provider.value() – Gabe Aug 18 '20 at 17:54
  • outside the context of created provider – akash maurya Aug 18 '20 at 18:58
  • Look at this post: https://stackoverflow.com/a/52249579/9609442 – Gabe Aug 18 '20 at 20:44
  • Is there any way to change values in provider state. I mean not to access them. I want to change and notify listeners outside a widget. please help – EDPChinthana Sep 02 '20 at 06:33
  • @EDPChinthana please ask a seperate question for this. The primary method for notifying listeners is just to call the `notifyListeners()` method in your provider when you have changed a public variable. – Gabe Sep 02 '20 at 16:29
  • @Gabe thanks for the reply I have asked a question but nobody answered it. I am looking for this for a few weeks. Now I am thinking my coding pattern may be the problem. – EDPChinthana Sep 03 '20 at 02:18
2

You should take a look at Riverpod. It fills out provider caveats, and gives you a bunch of new capabilities. You can access 'provider' values and functions defined in the class without context. It also allows you to have multiple 'providers' of the same type.

  • 3
    this kind of answer better be comment instead https://stackoverflow.com/help/how-to-answer – buncis Feb 02 '22 at 04:51
1

I moved from my api being an inheritedWidget to a provider. "The Flutter Complete Reference" suggested to avoid inheritWidget because of complexity. I start at the materialapp and wrap it around my api class containing my restful calls. I use the counter to see that the button has been pressed. Now, I can access my api calls in all objects in the materialapp tree.

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

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
    title: 'My app',
    theme: ThemeData(
         primarySwatch: Colors.blue,
    ),
    debugShowCheckedModeBanner: false,
    home: Provider<Api>(
        create: (context) => Api(),
        child: Home(title: 'my title')));
  }
}

class Home extends StatefulWidget {
 Home({Key key, @required this.title}) : super(key: key);
 final String title;
 @override
 _HomeState createState() => _HomeState();
 }

class _HomeState extends State<Home> {
 int _counter = 0;
 onLogin(BuildContext context) {
   LoginView param = new LoginView("abc@com",
    "xxx");

   Provider.of<Api>(context, listen: false)
      .addLogin(context, param)
    .then((value) {
    setState(() {
    _counter++;
    });
   }).catchError((error) {
    DialogCaller.showErrorDialog(context, 
error.toString()).then((value) {});
});
}

 @override
 Widget build(BuildContext context) {
  return Scaffold(
        appBar: AppBar(
          title: const Text('Example'),
        ),
        body: Container(
            child: Column(
          children: [
            TextButton.icon(
                label: Text("abc"),
                icon: Icon(Icons.web),
                onPressed: () {
                  onLogin(context);
                  setState(() {
                    _counter++;
                  });
                }),
            Text(
              '$_counter',
              style: 
 Theme.of(context).textTheme.headline4,
            )
          ],
        )));
 }
}
Golden Lion
  • 3,840
  • 2
  • 26
  • 35