pull down to REFRESH in Flutter

Basic Example

Below is a State class of a StatefulWidget, where:

  • a ListView is wrapped in a RefreshIndicator
  • numbersList state variable is its data source
  • onRefresh calls _pullRefresh function to update data & ListView
  • _pullRefresh is an async function, returning nothing (a Future<void>)
  • when _pullRefresh‘s long running data request completes, numbersList member/state variable is updated in a setState() call to rebuild ListView to display new data
import 'package:flutter/material.dart';
import 'dart:math';

class PullRefreshPage extends StatefulWidget {
  const PullRefreshPage();

  @override
  State<PullRefreshPage> createState() => _PullRefreshPageState();
}

class _PullRefreshPageState extends State<PullRefreshPage> {
  List<String> numbersList = NumberGenerator().numbers;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: RefreshIndicator(
        onRefresh: _pullRefresh,
        child: ListView.builder(
          itemCount: numbersList.length,
          itemBuilder: (context, index) {
            return ListTile(
              title: Text(numbersList[index]),
            );
          },),
      ),
    );
  }

  Future<void> _pullRefresh() async {
    List<String> freshNumbers = await NumberGenerator().slowNumbers();
    setState(() {
      numbersList = freshNumbers;
    });
    // why use freshNumbers var? https://stackoverflow.com/a/52992836/2301224
  }
}

class NumberGenerator {
  Future<List<String>> slowNumbers() async {
    return Future.delayed(const Duration(milliseconds: 1000), () => numbers,);
  }

  List<String> get numbers => List.generate(5, (index) => number);


  String get number => Random().nextInt(99999).toString();
}

Notes

  • If your async onRefresh function completes very quickly, you may want to add an await Future.delayed(Duration(seconds: 2)); after it, just so the UX is more pleasant.
  • This gives time for the user to complete a swipe / pull down gesture & for the refresh indicator to render / animate / spin indicating data has been fetched.

FutureBuilder Example

Here’s another version of the above State<PullRefreshPage> class using a FutureBuilder, which is common when fetching data from a Database or HTTP source:

class _PullRefreshPageState extends State<PullRefreshPage> {
  late Future<List<String>> futureNumbersList;

  @override
  void initState() {
    super.initState();
    futureNumbersList = NumberGenerator().slowNumbers();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: FutureBuilder<List<String>>(
        future: futureNumbersList,
        builder: (context, snapshot) {
          return RefreshIndicator(
            child: _listView(snapshot),
            onRefresh: _pullRefresh,
          );
        },
      ),
    );
  }

  Widget _listView(AsyncSnapshot snapshot) {
    if (snapshot.hasData) {
      return ListView.builder(
        itemCount: snapshot.data.length,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text(snapshot.data[index]),
          );
        },);
    }
    else {
      return Center(
        child: Text('Loading data...'),
      );
    }
  }

  Future<void> _pullRefresh() async {
    List<String> freshNumbers = await NumberGenerator().slowNumbers();
    setState(() {
      futureNumbersList = Future.value(freshNumbers);
    });
  }
}

Notes

  • slowNumbers() function is the same as in the Basic Example above, but the data is wrapped in a Future.value() since FutureBuilder expects a Future, but setState() should not await async data
  • according to RĂ©mi, Collin & other Dart/Flutter demigods it’s good practice to update Stateful Widget member variables inside setState() (futureNumbersList in FutureBuilder example & numbersList in Basic example), after its long running async data fetch functions have completed.
    • see https://stackoverflow.com/a/52992836/2301224
  • if you try to make setState async, you’ll get an exception
  • updating member variables outside of setState and having an empty setState closure, may result in hand-slapping / code analysis warnings in the future

Leave a Comment