Sorting a Future List in Flutter - flutter

I've been looking for a solution to sort a list (ascending and descending) On Button Press inside of a FutureBuilder, that is a Future<List>, but can't seem to understand how to define it as a List and then sort it on a button press. So I call the API, the API returns some dummy value, it's gets built in the Future Builder and in a ListView.builder, now I want sort the list by id (or by any type for that matter) but the method is not working because the list is null. The code:
API Call for the dummy data:
Future<List<Post>> fetchPosts() async {
List<Post> posts = [];
final response = await http.get('https://jsonplaceholder.typicode.com/posts');
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
var postsJson = jsonDecode(response.body);
for (int i = 0; i < postsJson.length; i++) {
posts.add(Post.fromJson(jsonDecode(response.body)[i]));
}
return posts;
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load posts');
}
}
The Future Builder:
List<Post> posts = []; /// if a define it like this, the value is always null
Future<List<Post>> futurePosts;
#override
void initState() {
super.initState();
futurePosts = fetchPosts();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Column(
children: [
MaterialButton(color: Colors.grey, onPressed: (){
// here I am setting set to compare the values of all IDs so it can be sorted ascending and descending by number of ID every time I press the button
setState(() {
posts.sort((a, b) => a.id.compareTo(b.id));
});
},),
Container(
height: 1000,
child: FutureBuilder<List<Post>>(
future: futurePosts,
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
shrinkWrap: true,
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return Text('${snapshot.data[index].id}')
},
);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
return Container();
},
),
But it seems my understanding and code is not working for me at this point. Any help is appreciated, thanks in advance!

You can move your posts.sort((a, b) => a.id.compareTo(b.id)); inside your Future function, before returning posts. And change the setState, to change the state of a boolean, which sorts or not.
You can change like this:
//define a boolen
bool _isSorted =false;
Future<List<Post>> fetchPosts(bool sortORnot) async {
List<Post> posts = [];
final response = await http.get('https://jsonplaceholder.typicode.com/posts');
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
var postsJson = jsonDecode(response.body);
for (int i = 0; i < postsJson.length; i++) {
posts.add(Post.fromJson(jsonDecode(response.body)[i]));
}
if (sortORnot) {posts.sort((a, b) => a.id.compareTo(b.id));}// this will sort only if you wanted your list sorted.
return posts;
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load posts');
}
}
Change your FutureBuilder to this:
FutureBuilder<List<Post>>(
future:_isSorted? fetchPosts(true):fetchPosts(false),
builder: (context, snapshot) {
and setState to this:
setState(() {
_isSorted = !_isSorted; //this flips the value whenever you press it.
});
Now, in your future builder, you should get the posts sorted, can you try this?

Something like this, I think, should work:
List<Post> posts;
#override
void initState() {
super.initState();
fetchPosts().then((items) {
setState(() {
posts = items;
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Column(children: [
MaterialButton(
color: Colors.grey,
onPressed: () {
setState(() {
if (posts != null) {
posts = posts.toList();
posts.sort((a, b) => a.id.compareTo(b.id));
}
});
},
),
Container(
height: 1000,
child: (posts != null)
? ListView.builder(
shrinkWrap: true,
itemCount: posts.length,
itemBuilder: (context, index) {
return Text('${posts[index].id}');
},
)
: Container(),
)
]),
),
),
);
}
Your posts field is always empty because you never assign data to that field. And this is the main problem. Try it out.

Related

ListView infinite loop when parsing data from API response

I'm trying to read data from some mock endpoint. Mock endpoint I'm invoking (HTTP GET) is here.
Essentially, the JSON structure is result > toolList[] > category > tools[]. I'd like to display these items on my page in such a way that the category name is displayed first, then items belonging to that category under it. I am trying to achieve this with ListView.builder but I somehow managed to get some sort of infinite loop and the items keep getting populated until my device freezes.
What I'm trying to achieve:
Category Title
Item 1
Item 2
Category Title 2
Item 1
Item 2
Itme 3
And finally the Widget:
class OpticsSelectorWidget extends StatefulWidget {
const OpticsSelectorWidget({Key key}) : super(key: key);
#override
_OpticsSelector createState() => _OpticsSelector();
}
class _OpticsSelector extends State<OpticsSelectorWidget> {
PageController pageViewController;
final scaffoldKey = GlobalKey<ScaffoldState>();
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: StandardAppbarWidget(appBarTitle: "some title"),
body: SizedBox(
child: FutureBuilder<ApiCallResponse>(
future: ConfigurationController.getOpticsTools2(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: SizedBox(
width: 50,
height: 50,
child: CircularProgressIndicator(
color: Colors.red,
),
),
);
}
final gridViewGetToolsOpticsResponse = snapshot.data;
var toolCategories = getJsonField(
gridViewGetToolsOpticsResponse.jsonBody,
r'''$.result.toolList''',
).toList();
return Builder(
builder: (context) {
return ListView.builder(itemBuilder: (context, itemIndex) {
final widgets = <Widget>[];
for (int i = 0; i < toolCategories.length; i++) {
var currentToolCategory = getJsonField(
toolCategories[i],
r'''$.category''',
);
widgets.add(Text(
currentToolCategory,
style: Colors.white,
));
var toolListInCategory = getJsonField(
toolCategories[itemIndex],
r'''$.tools''',
);
for (int j = 0; j < toolListInCategory.length - 1; j++) {
var toolDisplayName = getJsonField(
toolListInCategory[j],
r'''$.displayName''',
);
widgets.add(Text(toolDisplayName));
}
}
return SingleChildScrollView(
child: Column(
children: widgets,
));
});
},
);
},
),
),
);
}
}
I'm especially confused about the itemIndex expression. That number I thought would be the item count that I receive from my API call, but I guess I'm mixing something badly.
If it helps, here's the bit where I'm making the API call. But feel free to just grab the JSON your way (from mock response)
static Future<ApiCallResponse> getOpticsTools2() async {
HttpOverrides.global = new MyHttpOverrides();
var client = http.Client();
try {
var response = await client.get(Uri.https('stoplight.io'
, "mocks/ragingtortoise/test/82311857/configuration/tools/optics"));
return createResponse(response, true);
} finally {
client.close();
}
}
static ApiCallResponse createResponse(http.Response response, bool returnBody) {
var jsonBody;
try {
jsonBody = returnBody ? json.decode(response.body) : null;
} catch (_) {}
return ApiCallResponse(jsonBody, response.statusCode);
}
And the return type, which is ApiCallResponse:
class ApiCallResponse {
const ApiCallResponse(this.jsonBody, this.statusCode);
final dynamic jsonBody;
final int statusCode;
bool get succeeded => statusCode >= 200 && statusCode < 300;
}
Finally adding the screen recording of what's happening, if it helps.
In here builder you should use,itemCount parameter
ListView.builder(
itemCount: list.length,
itemBuilder: (context, index) {
return Your Widget;
}),
Create a state variable for future and include itemCount: list.length,
final myFuture = ConfigurationController.getOpticsTools2();
And use it on
child: FutureBuilder<ApiCallResponse>(
future: myFuture ,
builder: (context, snapshot) {
I struggled for so long but clearly, the issue was not passing in the itemCount argument into the ListView.builder() method. Also, the outer loop was invalid as now I need to use the actual itemIndex within the builder. Thanks for pointing out the itemCount all! Here's the fixed code and the solution in case anyone needs it later.
#override
Widget build(BuildContext context) {
final opticsToolsMockResponse = ConfigurationController.getOpticsTools2();
return Scaffold(
backgroundColor: Colors.black,
appBar: StandardAppbarWidget(appBarTitle: "some title"),
body: SizedBox(
child: FutureBuilder<ApiCallResponse>(
future: opticsToolsMockResponse,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: SizedBox(
width: 50,
height: 50,
child: CircularProgressIndicator(
color: Colors.red,
),
),
);
}
final gridViewGetToolsOpticsResponse = snapshot.data;
var toolCategories = getJsonField(
gridViewGetToolsOpticsResponse.jsonBody,
r'''$.result.toolList''',
).toList();
return Builder(
builder: (context) {
return ListView.builder(
itemCount: toolCategories.length,
itemBuilder: (context, itemIndex) {
final widgets = <Widget>[];
var currentToolCategory = getJsonField(
toolCategories[itemIndex],
r'''$.category''',
);
widgets.add(Text(
currentToolCategory,
style: Colors.white,
));
var toolListInCategory = getJsonField(
toolCategories[itemIndex],
r'''$.tools''',
);
for (int j = 0; j < toolListInCategory.length; j++) {
var toolDisplayName = getJsonField(
toolListInCategory[j],
r'''$.displayName''',
);
widgets.add(Text(toolDisplayName));
}
return SingleChildScrollView(
child: Column(
children: widgets,
));
});
},
);
},
),
),
);
}
You just forgot to specify the size of the list, you should do it with the itemCount property in the ListView.builder widget
itemCount: list.length,

Future builder returning length as null

I am retreiving data from cloud firestore and using Future builder and Listview Builder to display the data. But i am getting null values in the Listview builder i.e displaying the CircularProgressIndicator always.Can't figure out the problem.Any solution will be of great help.
The print(values) function prints out: [9,8] successfully
This is the code i implemented:
Future<List> getassignment() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
final name = prefs.getString('orgname') ?? '';
print(name);
var query = FirebaseFirestore.instance.collection('Org').doc(name).collection('Login').doc(FirebaseAuth.instance.currentUser.uid);
query.snapshots().forEach((doc) {
List values = List.from(doc.data()['fields']['class']);
print(values);
return values;
});
}
// void getlist() async{
// await getassignment();
// }
#override
void initState() {
// getlist();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xFF1976D2),
body: FutureBuilder(
future: getassignment(),
builder: (context,snapshot){
List list = snapshot.data;
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
} else{
return Container(
child: ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, position) {
return GestureDetector(
onTap: (){
Navigator.of(context).push(MaterialPageRoute<Null>(
builder: (BuildContext context){
return new SubjectList(
clas: list[position].toString(),
);
}
));
},
child: Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(list[position].toString(), style: TextStyle(fontSize: 22.0),),
),
),
);
},
),
);
}
},
),
);
}
You are assigning and returning data inside of foreach loop. So that won't return anything.
// try adding await in this line.
var query = await FirebaseFirestore.instance.collection('Org').doc(name).collection('Login').doc(FirebaseAuth.instance.currentUser.uid);
List values = query.snapshots().forEach((doc) => List.from(doc.data()['fields']['class']));
print(values);
return values;
You need to do something like this.

Flutter BLoC implementation with streamBuilder

I have a problem with my BLoC implementation, I have this code in synchronize.dart:
...
class _SynchronizeState extends State<Synchronize> {
UserBloc userBloc;
//final dbRef = FirebaseDatabase.instance.reference();
#override
Widget build(BuildContext context) {
userBloc = BlocProvider.of(context);
return Scaffold(
resizeToAvoidBottomPadding: false,
body: Container(
...
),
child: StreamBuilder(
stream: dbRef.child('info_tekax').limitToLast(10).onValue,
builder: (context, snapshot) {
if(snapshot.hasData && !snapshot.hasError){
Map data = snapshot.data.snapshot.value;
List keys = [];
data.forEach( (index, data) => keys.add(index) );
return ListView.builder(
itemCount: data.length,
itemBuilder: (context, index) => SynchronizeItem(title: keys[index], bottom: 10, onPressed: (){ print(keys[index]); })
);
}else{
return Container(
child: Center(
child: Text('Loading...'),
),
);
}
}
),
),
);
}
}
The previos code, works correctly, but i want implemente bloc Pattern, i have userBloc then i want to put this
userBloc.getDevicesForSinchronized()
instead of
dbRef.child('info_tekax').limitToLast(10).onValue,
my problem is this:
void getDevicesForSynchronized() {
return dbRef.child(DEVICES).limitToLast(10).onValue;
}
i get this error **A vaue of type 'Stream' can't be returned from method 'getDevicesForSynchronized' because it has a return type of 'void'
The error is very clear, but i don't know what is type that i need return, try:
Furure<void> getDevicesForSynchronized() async {
return await dbRef.child(DEVICES).limitToLast(10).onValue;
}
or
Furure<void> getDevicesForSynchronized() async {
dynamic result = await dbRef.child(DEVICES).limitToLast(10).onValue;
}
and another solutions, but I don't know how return correctly value for use in the StreamBuilder
From the error message you can see that the return type is Stream. Change your method like:
Future<Stream> getDevicesForSynchronized() async {
return dbRef.child(DEVICES).limitToLast(10).onValue;
}

Flutter: how to filter listview with the data loaded from API

I have a list populated with a Future builder. The items are loaded correctly in the list from API.
Following is the relevant part of the code. I have a textfield in an appbar, which I want to use to filter the list.
List newList = List();
List originalList = List();
bool _showSearchBox = false;
TextEditingController _textController = TextEditingController();
Future _future;
#override
void initState() {
_future = commonApiProvider.fetchUserList(offset, widget.selectedDate);
super.initState();
}
#override
Widget build(BuildContext context) {
size = Screen(MediaQuery.of(context).size);
loadMoreNewStatus = ItemLoadMoreStatus.LOADING;
return Scaffold(
backgroundColor: Color(0xfff0f0f0),
appBar: AppBar(
automaticallyImplyLeading: _showSearchBox == true ? false : true,
backgroundColor: CustomColors.absentTileColor,
elevation: 1,
title:
_showSearchBox == true ? _buildSearchWidget() : Text("Absent List"),
actions: <Widget>[
_showSearchBox == false ? _buildSearchIcon() : Container(),
],
),
body: FutureBuilder(
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.none &&
snapshot.hasData == null) {
return Text("Records not found for selected date.");
} else if (snapshot.hasData) {
return _buildListChild(snapshot);
} else if (snapshot.hasError) {
return Text(snapshot.error.toString());
}
},
future: _future,
),
);
}
Widget _buildListChild(AsyncSnapshot snapshot) {
var data = snapshot.data.d;
newList = json.decode(data.userList);
originalList = json.decode(data.userList);
return RefreshIndicator(
key: _refreshIndicatorKey,
child: NotificationListener(
onNotification: onNotificationHandler,
child: ListView.builder(
padding: EdgeInsets.only(top: size.getSizePx(10)),
scrollDirection: Axis.vertical,
shrinkWrap: true,
physics: const BouncingScrollPhysics(),
itemCount: newList.length,
controller: scrollContainer,
itemBuilder: (context, index) {
if (index == newList.length) {
return _buildProgressIndicator();
} else {
loadMoreNewStatus = ItemLoadMoreStatus.STABLE;
animationController.forward();
return cardView(newList[index]);
}
}),
),
onRefresh: _refreshStuffs,
);
}
Widget cardView(userList){
//build list items here.
}
bool onNotificationHandler(ScrollNotification notification){
//stuffs here
}
_refreshStuffs(){
//code to refresh list.
}
Widget _buildSearchWidget(){
return Container(
child: TextField(
controller: _textController,
style: TextStyle(fontSize: 14.0, color: Colors.grey[800]),
onChanged: onSearchTextChanged,
);
);
}
onSearchTextChanged(String text) async {
List tempSearchList = List();
tempSearchList.addAll(originalList);
if (text.isNotEmpty) {
List tempListData = List();
tempSearchList.forEach((item) {
String empName = item["empname"];
if (empName.toLowerCase().contains(text.toLowerCase())) {
tempListData.add(item);
}
});
setState(() {
newList.clear();
newList.addAll(tempListData);
});
return;
} else {
setState(() {
newList.clear();
newList.addAll(originalList);
});
}
}
Problem
The problem is that above code is not working, the list doesn't change at all. If I debug method onSearchTextChanged it works very well. I have cleared newList on this method as well, but doesn't seem to work. Can anybody help how to achieve filter?
The idea here is: Once FutureBuilder completes, it doesn't get rebuild.
I hope the code below helps. Let me know if your problem exists.
class _MyHomePageState extends State<MyHomePage> {
var items = [];
#override
void initState() {
callApi();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: YourFilteringTextField(),
),
body: ListView.builder(
itemBuilder: (context, position) {
return Text(items[position]);
},
itemCount: items.length,
),
);
}
callApi() {
//call api to get your latest items
setState(() {
// items= itemsFetchedFromApi;
});
}
filter(query) {
//applyFilter
setState(() {
// items= itemsAfterFiltering;
});
}
}

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();
}
}
},
);
}
}
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
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;
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
Easy method: you can just use Pull Down to Refresh Package - https://pub.dev/packages/pull_to_refresh
In Non-scrollable list view, RefreshIndicator does not work, so you have to wrap your widget with Stack for implementing pull down to refresh.
RefreshIndicator(
onRefresh: () {
// Refresh Functionality
},
child: Stack(
children: [
ListView(
padding: EdgeInsets.zero,
shrinkWrap: true,
children: [
SizedBox(
height: MediaQuery.of(context).size.height,
)
],
),
// Your Widget
],
);
),
I am working on a huge project which contains CustomScrollView, NestedScrollView, ListView, etc I tried every answer above and all of the answers use RefreshIndicator from flutter SDK. It doesn't work entirely with my app because I also have horizontal scroll views. So in order to implement it I had to use NestedScrollView on almost every screen. Then I came to know about liquid_pull_to_refresh, applied it to the top widget, and WOLAAH! If you need a separate logic for each screen then use it at the top of each screen but in my case, I'm refreshing the whole project's data.