Basic Example
Below is a State class of a StatefulWidget, where:
- a
ListViewis wrapped in aRefreshIndicator numbersListstate variable is its data sourceonRefreshcalls_pullRefreshfunction to update data & ListView_pullRefreshis an async function, returning nothing (aFuture<void>)- when
_pullRefresh‘s long running data request completes,numbersListmember/state variable is updated in asetState()call to rebuildListViewto 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
onRefreshfunction completes very quickly, you may want to add anawait 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 aFuture.value()sinceFutureBuilderexpects aFuture, butsetState()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()(futureNumbersListin FutureBuilder example &numbersListin 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
setStateand having an emptysetStateclosure, may result in hand-slapping / code analysis warnings in the future