'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 a RefreshIndicator
    • words state variable is its data source
  • onRefresh calls _pullRefresh function to update ListView
    • _pullRefresh is an async function, returning nothing (a Future<void>)
    • when _pullRefresh's long running data request completes, words member/state variable is updated in a setState() call to rebuild ListView 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 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 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 a Future.value() since FutureBuilder expects a Future
  • 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 empty setState 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