How to fetch network data on provider context change in Flutter

Issue

I have a Flutter application with some kinda weird functionality, but it’s required one. The application has BottomNavigationBar with pages wrapped in IndexedStack, so that if you go to other page of this IndexedStack, the pages do not reload and stay persistent.

class _MyHomePageState extends State<MyHomePage> {
//...

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: IndexedStack(
        index: selectedIndex,
        children: bottomBarPages(),
      ),
      bottomNavigationBar: BottomNavigationBar(
        items: navItems,
        currentIndex: selectedIndex,
        onTap: _onNavItemTapped,
      ),
    );
  }
}

One of these pages is a ProfilePage, where you can choose between list of users. I use ChangeNotifier that looks like this:


class MainUserInfo extends ChangeNotifier {
  List<UserInfo> _users;
  UserInfo _chosenUser;
  //... some get/set/etc methods here
}

One of other BottomNavigationBar pages TheOtherPage depends on the chosen user (_chosenUser), on initState it loads data from the Net:

class TheOtherPage extends StatefulWidget {
//...

  bool isLoading = true;

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

  void getAccountData() async {
    //some fetch happening here
    
    setState(() {
      model = response.data;
      isLoading = false;
    });
  }
}

The thing is that after field _chosenUser changes, I need TheOtherPage to load data of chosen user from the Net. But I don’t know how to handle it. If I use context.read or context.select it surely tracks changing _chosenUser, but doesn’t fetch data of newly chosen one:

var mainUserInfo = context.read<MainUserInfo>();
//or
var chosenUser = context.select<MainUserInfo, UserInfo>((userInfo) => userInfo.chosenUser);

And I don’t wanna save data needed for TheOtherPage in UserInfo object, because there are just too much data, and loading so much data for every user in MainUserInfo._users is not an option.
Also I would to be able to keep track of loading state, because using "bool Isloading" I can show some fancy placeholders while required data is being fetched.

So the general question is how to make TheOtherPage fetch data on changing field of ChangeNotifier on ProfilePage. Maybe my whole approach is bad and I don’t need to use Provider and ChangeNotifier here, I would like to see any suggestions. Thanks

Solution

This is a typical case that provider try to solve: share data between 2 widgets

Here is your tree structure:

Scaffold
 \ IndexedStack
    \ ProfilePage  <- share MainUserInfo 
    \ TheOtherPage  <- share MainUserInfo 

The problem is that you want TheOtherPage to listen to MainUserInfo which it doesn’t. (Because the initState() in a statefulwidget will be called only once, even the state is changed)

I suggest you can listen to the provider inside the build method and use FutureBuilder to handle the loading state:

class _TheOtherPageState extends State<TheOtherPage> {
//...

  @override
  void initState() {
    super.initState();
    // do nothing here
  }

  @override
  Widget build(BuildContext context) {      
    // rebuild everytime the MainUserInfo get notified by watching it
    // final mainUserInfo = context.watch<MainUserInfo>();

    // rebuild everytime the chosenUser change inside MainUserInfo
    final chosenUser = context.select<MainUserInfo, UserInfo>((userInfo) => userInfo.chosenUser);

    return FutureBuilder(
      future: getAccountData(chosenUser).
      builder: (BuildContext context, AsyncSnapshot snapshot) {
        if(snapshot.connectionState == ConnectionState.done && snapshot.hasData){
          model = snapshot.data;

          // return a page with the model
        } else {

          // return the loading placeholders 
        }
      }
    );
  }

  Future getAccountData(user) async {
    //some fetch happening here with the user
    // ...

    return response.data;
  }
}

There is one more thing you can try: use const keyword
(How to deal with unwanted widget build?)

  ...
  body: IndexedStack(
    index: selectedIndex,
    children: [
      ProfilePage(),
      const TheOtherPage(),    // here
    ],
  ),
  ...

class TheOtherPage extends StatefulWidget {
  const TheOtherPage({Key key}) : super(key: key);   // and here

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

Answered By – yellowgray

Answer Checked By – Clifford M. (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *