Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flutter: How to access 'provider' values in a function outside the build context of the child widget?

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,
      ),
    );
  }
}
like image 703
Omar Ellakany Avatar asked Apr 26 '20 20:04

Omar Ellakany


3 Answers

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.

like image 83
Gabe Avatar answered Nov 15 '22 11:11

Gabe


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,
            )
          ],
        )));
 }
}
like image 45
Golden Lion Avatar answered Nov 15 '22 11:11

Golden Lion


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.

like image 28
José Luis Rodríguez Fragoso Avatar answered Nov 15 '22 10:11

José Luis Rodríguez Fragoso