Basic Example
Below is a State class of a StatefulWidget, where:
- a
ListView
is wrapped in aRefreshIndicator
numbersList
state variable is its data sourceonRefresh
calls_pullRefresh
function to update data & ListView_pullRefresh
is an async function, returning nothing (aFuture<void>
)- when
_pullRefresh
‘s long running data request completes,numbersList
member/state variable is updated in asetState()
call to rebuildListView
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 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()
sinceFutureBuilder
expects 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()
(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 emptysetState
closure, may result in hand-slapping / code analysis warnings in the future