pull down to REFRESH in Flutter
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
Try this:
onRefresh: () {
setState(() {});
}}
instead of onRefresh:getReport
reportList
field is Future
which returns its value once. So, when you call getReport
again it changes nothing. Actually, more correctly it'll be with Stream
and StreamBuilder
instead of Future
and FutureBuilder
. But for this code it can be shortest solution
Not sure about futures, but for refresh indicator you must return a void so Use something like
RefreshIndicator(
onRefresh: () async {
await getData().then((lA) {
if (lA is Future) {
setState(() {
reportList = lA;
});
return;
} else {
setState(() {
//error
});
return;
}
});
return;
},
Try this and let me know!
EDIT:
Well, then just try this inside you refresh method
setState(() {
reportList = getReport();
});
return reportList;