The argument type 'Future<List<User>>' can't be assigned to the parameter type 'Future<void> Function()' in onRefresh when using RefreshIndicator

I am having a Future that is fetching and populating users below

Future<List<User>> _fetchUsersListUsingLoop() async {
    try {
      
      var response = await http.get(
          Uri.parse(
              "https://api.json-generator.com/templates/Eh5AlPjYVv6C/data"),
          headers: {
            "Content-Type": "application/json",
            "Authorization": "Bearer tltsp6dmnbif01jy9xfo9ssn4620u89xhuwcm5t3",
          });


      List<User> usersList = [];

      for (var u in json.decode(response.body)) {
        User user = User(u["email"], u["about"], u["name"], u["picture"],
            u["index"], u["imageFetchType"]);

        usersList.add(user);
      }

      return usersList;
    } catch (e) {
      log("FetchUsersListUsingLoopException $e");

      rethrow;
    }
  }

And below is how i am using future in FutureBuilder to call _fetchUsersListUsingLoop() and then use ListView.builder inside RefreshIndicator to show users in the list

body: SizedBox(
        child: FutureBuilder(
            future: _fetchUsersListUsingLoop(),
            builder: (BuildContext context, AsyncSnapshot asyncSnapshot) {
              if (asyncSnapshot.data == null) {
                return const Center(child: CircularProgressIndicator());
              } else {
                return RefreshIndicator(
                  // background color
                    backgroundColor: Colors.white,
                    // refresh circular progress indicator color
                    color: Colors.green,
                    onRefresh: _fetchUsersListUsingLoop(),
                    child: ListView.builder(
                      itemCount: asyncSnapshot.data.length,
                      itemBuilder: (BuildContext context, int index) {
                        return ListTile(
                          contentPadding: const EdgeInsets.all(10),
                          leading: CircleAvatar(
                            backgroundImage:
                                NetworkImage(asyncSnapshot.data[index].picture),
                          ),
                          title: Text(asyncSnapshot.data[index].name),
                          subtitle: Text(
                              "${asyncSnapshot.data[index].email} \nUsing NetworkImage with backgroundImage"),
                        );
                      },
                    ));
              }
            }),
      ),

I am getting The argument type 'Future<List<User>>' can't be assigned to the parameter type 'Future<void> Function()'. error in this line onRefresh: _fetchUsersListUsingLoop(), How can i be able to call _fetchUsersListUsingLoop() again when i swipe down to refresh


Solution 1:

Instead of calling _fetchUsersListUsingLoop you should use setState to cause a rebuild so that FutureBuilder fetches the data again.

onRefresh: () => setState(() {})

That being said it's best to store your future in initState and update this future when needed by calling setState. This is from documentation:

The future must have been obtained earlier, e.g. during State.initState, State.didUpdateWidget, or State.didChangeDependencies. It must not be created during the State.build or StatelessWidget.build method call when constructing the FutureBuilder. If the future is created at the same time as the FutureBuilder, then every time the FutureBuilder's parent is rebuilt, the asynchronous task will be restarted.

Future<List<User>>? _usersFuture;

@override
  void initState() {
    super.initState();
    _usersFuture = _fetchUsersListUsingLoop();
}

Then:

onRefresh: () {
    setState(() {
      _usersFuture = _fetchUsersListUsingLoop();
    });
  }

Solution 2:

When you do this

onRefresh: _fetchUsersListUsingLoop(),

You are telling dart to call _fetchUsersListUsingLoop and expect it to return a function, then to call said function, what you want to do instead is tell it to just call _fetchUsersListUsingLoop:

onRefresh: _fetchUserListUsingLoop,

or ìf that doesn't work:

onRefresh: () async => _fetchUserListUsingLoop(),

But the problem is that this will not actually work, why? Because the future builder is already completed and nothing will change that, to fix your issue; you can reassign the future like this:

You need to declare a future:

late Future<List<User>> _user;
        child: FutureBuilder(
            future: _users,
            builder: (context, asyncSnapshot) {
              ...
                    onRefresh: _updateUsersFuture,
              ...

Then somewhere in your class you do:

Future<void> _updateUsersFuture() async {
  final newUsers = await _fetchUserListUsingLoop();
  setState(() => _users = newUsers);
}

and on initstate:

@override
void initState() {
  _users = _fetchUsersListUsingLoop();
  super.initState();
}