'Prevent repeated API calls upon Changing Screens in Flutter

I was looking for alternatives in which the data is loaded from API only once and stays that way if I move to and fro that screen and I found one in using InheritedWidget. However, I'm now getting the below error and I cannot figure out how to get rid of this.

The getter 'users' was called on null.
Receiver: null
Tried calling: users

The errors are marked as comments in the below:

My code:

InheritedWidget Class

class InheritedUsers extends InheritedWidget {
  final UsersList users;

  InheritedUsers({required Key key, required Widget child})
      : assert(child != null),
        users = UsersList(),
        super(key: key, child: child);

  static UsersList of(BuildContext context) =>
      (context.dependOnInheritedWidgetOfExactType(aspect: InheritedUsers)
              as InheritedUsers)
          .users;

  @override
  bool updateShouldNotify(covariant InheritedWidget oldWidget) => false;
}

class UsersList {
  late List<User> listOfUsers;
  Future<List<User>> get userList async {
    return listOfUsers = await UsersApi.getUsers();
  }
}

class UsersApi with ChangeNotifier {
  static Future<List<User>> getUsers() async {
    // List<User> list = [];
    // late final body;
    final url =
        'https://firebasestorage.googleapis.com/v0/b/web-johannesmilke.appspot.com/o/other%2Fvideo126%2Fusers.json?alt=media';
    final response = await http.get(Uri.parse(url));
    final body = json.decode(response.body);

    return body.map<User>(User.fromJson).toList();
  }
}

UserNetworkPage widget

class UserNetworkPage extends StatefulWidget {
  UserNetworkPageState createState() => UserNetworkPageState();
}

class UserNetworkPageState extends State<UserNetworkPage> {
  late final Future<List<User>> result;

  @override
  void didChangeDependencies() {
    // TODO: implement didChangeDependencies
    result = InheritedUsers.of(context).userList;        //This is where the error gets thrown
    super.didChangeDependencies();
  }

  @override
  Widget build(BuildContext context) => Scaffold(
        body: FutureBuilder<List<User>>(
          future: result,
          builder: (context, snapshot) {
            final users = snapshot.data;

            switch (snapshot.connectionState) {
              case ConnectionState.waiting:
                return Center(child: CircularProgressIndicator());
              default:
                if (snapshot.hasError) {
                  return Center(child: Text('Some error occurred!'));
                } else {
                  return buildUsers(users!);
                }
            }
          },
        ),
      );

  Widget buildUsers(List<User> users) => ListView.builder(
        physics: BouncingScrollPhysics(),
        itemCount: users.length,
        itemBuilder: (context, index) {
          final user = users[index];

          return ListTile(
            onTap: () => Navigator.of(context).push(MaterialPageRoute(
              builder: (BuildContext context) => UserPage(user),
            )),
            leading: CircleAvatar(
              backgroundImage: NetworkImage(user.urlAvatar),
            ),
            title: Text(user.username),
            subtitle: Text(user.email),
          );
        },
      );
}


Solution 1:[1]

What you need at this point is a state management solution. There's a lot of them, and I'm pretty sure they all use InheritedWidget underneath.

Introduction to state management

List of state management approaches

I personally recommend Riverpod, Provider or BLoC.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 MickaelHrndz