'pull down to REFRESH in Flutter
My dashboard code looks like this,
Here I am doing get req in getReport method, I have added the RefreshIndicator
in the code which when pulled down inside container should do the refresh, there I am calling my getData(), But I am not getting the refreshed content, I am adding my code below, let me know if anywhere I made a mistake.
below my dashboard.dart
class Window extends StatefulWidget {
@override
_WindowState createState() => _WindowState();
}
class _WindowState extends State<Window> {
Future reportList;
@override
void initState() {
super.initState();
reportList = getReport();
}
Future<void> getReport() async {
http.Response response =
await http.get(reportsListURL, headers: {"token": "$token"});
switch (response.statusCode) {
case 200:
String reportList = response.body;
var collection = json.decode(reportList);
return collection;
case 403:
break;
case 401:
return null;
default:
return 1;
}
}
getRefreshScaffold() {
return Center(
child: RaisedButton(
onPressed: () {
setState(() {
reportList = getReport();
});
},
child: Text('Refresh, Network issues.'),
),
);
}
getDashBody(var data) {
double maxHeight = MediaQuery.of(context).size.height;
return Column(
children: <Widget>[
Container(
height: maxHeight - 800,
),
Container(
margin: new EdgeInsets.all(0.0),
height: maxHeight - 188,
child: new Center(
child: new RefreshIndicator( //here I am adding the RefreshIndicator
onRefresh:getReport, //and calling the getReport() which hits the get api
child: createList(context, data),
),),
),
],
);
}
Widget createList(BuildContext context, var data) {
Widget _listView = ListView.builder(
itemCount: data.length,
itemBuilder: (context, count) {
return createData(context, count, data);
},
);
return _listView;
}
createData(BuildContext context, int count, var data) {
var metrics = data["statistic_cards"].map<Widget>((cardInfo) {
var cardColor = getColorFromHexString(cardInfo["color"]);
if (cardInfo["progress_bar"] != null && cardInfo["progress_bar"]) {
return buildRadialProgressBar(
context: context,
progressPercent: cardInfo["percentage"],
color: cardColor,
count: cardInfo["value"],
title: cardInfo["title"],
);
} else {
return buildSubscriberTile(context, cardInfo, cardColor);
}
}).toList();
var rowMetrics = new List<Widget>();
for (int i = 0; i < metrics.length; i += 2) {
if (i + 2 < metrics.length)
rowMetrics.add(Row(children: metrics.sublist(i, i + 2)));
else
rowMetrics.add(Row(children: [metrics[metrics.length - 1], Spacer()]));
}
return SingleChildScrollView(
child: LimitedBox(
// maxHeight: MediaQuery.of(context).size.height / 1.30,
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: rowMetrics,
),
),
);
}
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: reportList,
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
case ConnectionState.active:
return Center(
child: CircularProgressIndicator(),
);
case ConnectionState.done:
var data = snapshot.data;
if (snapshot.hasData && !snapshot.hasError) {
return getDashBody(data);
} else if (data == null) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text("Timeout! Log back in to continue"),
Padding(
padding: EdgeInsets.all(25.0),
),
RaisedButton(
onPressed: () {
setState(() {
token = null;
});
Navigator.of(context).pushReplacement(
CupertinoPageRoute(
builder: (BuildContext context) => LoginPage()),
);
},
child: Text('Login Again!'),
),
],
),
);
} else {
getRefreshScaffold();
}
}
},
);
}
}
Solution 1:[1]
Basic Example
Below is a State class of a StatefulWidget, where:
- a
ListView
is wrapped in aRefreshIndicator
words
state variable is its data source
onRefresh
calls_pullRefresh
function to update ListView_pullRefresh
is an async function, returning nothing (aFuture<void>
)- when
_pullRefresh
's long running data request completes,words
member/state variable is updated in asetState()
call to rebuildListView
to display new data
import 'package:english_words/english_words.dart';
class _PullToRefreshPageState extends State<PullToRefreshPage> {
List<WordPair> words = generateWordPairs().take(5).toList();
@override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: _pullRefresh,
child: ListView.builder(
itemCount: words.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(words[index].asPascalCase),
);
},),
);
}
Future<void> _pullRefresh() async {
List<WordPair> freshWords = await WordDataSource().getFutureWords(delay: 2);
setState(() {
words = freshWords;
});
// why use freshWords var? https://stackoverflow.com/a/52992836/2301224
}
}
class WordDataSource {
Future<List<WordPair>> getFutureWords({int size = 5, int delay = 5}) async {
await Future.delayed(Duration(seconds: delay));
return generateWordPairs().take(5).toList();
}
}
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 example, using a FutureBuilder, which is common when fetching data from a Database or HTTP source
class _PullToRefreshFuturePageState extends State<PullToRefreshPage> {
Future<List<WordPair>> futureWords;
@override
void initState() {
super.initState();
futureWords = WordDataSource().getFutureWords(delay: 2);
}
@override
Widget build(BuildContext context) {
return FutureBuilder<List<WordPair>>(
//initialData: [],
future: futureWords,
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].asPascalCase),
);
},);
}
else {
return Center(
child: Text('Loading data...'),
);
}
}
Future<void> _pullRefresh() async {
List<WordPair> freshFutureWords = await WordDataSource().getFutureWords(delay: 2);
setState(() {
futureWords = Future.value(freshFutureWords);
});
}
}
Notes
getFutureWords()
function is the same as in the Basic Example above, but the data is wrapped in aFuture.value()
since FutureBuilder expects aFuture
- according to RĂ©mi, Collin & other Dart/Flutter demigods it's good practice to update Stateful Widget member variables inside
setState()
(futureWords
in FutureBuilder example &words
in Basic example), after its long running async data fetch functions have completed. - 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
Solution 2:[2]
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;
Solution 3:[3]
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
Solution 4:[4]
Easy method: you can just use Pull Down to Refresh Package - https://pub.dev/packages/pull_to_refresh
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Baker |
Solution 2 | |
Solution 3 | |
Solution 4 | Mano Haran |