52

I am loading data when widget is loading like the code below. Once the UI is fully loaded, I like to add one refresh button to reload the data again.

How can I refresh the view ?

  class _MyHomePageState extends State<MyHomePage> {

      @override
      Widget build(BuildContext context) {
        var futureBuilder = new FutureBuilder(
          future: _getData(),
          builder: (BuildContext context, AsyncSnapshot snapshot) {
            switch (snapshot.connectionState) {
              case ConnectionState.none:
              case ConnectionState.waiting:
                return new Text('loading...');
              default:
                if (snapshot.hasError)
                  return new Text('Error: ${snapshot.error}');
                else
                  return createListView(context, snapshot);
            }
          },
        );

        return new Scaffold(
          appBar: new AppBar(
            title: new Text("Home Page"),
          ),
          body: futureBuilder,
        );
      }

      Future<List<String>> _getData() async {
        var values = new List<String>();

        await new Future.delayed(new Duration(seconds: 5));

        return values;
      }

      Widget createListView(BuildContext context, AsyncSnapshot snapshot) {

      }
    }
fifa chapman
  • 531
  • 1
  • 4
  • 9

5 Answers5

53
Widget createListView(BuildContext context, AsyncSnapshot snapshot) {
  RaisedButton button = RaisedButton(
    onPressed: () {
      setState(() {});
    },
    child: Text('Refresh'),
  );
  //.. here create widget with snapshot data and with necessary button
}
Watanabe.N
  • 1,549
  • 1
  • 14
  • 37
Andrii Turkovskyi
  • 27,554
  • 16
  • 95
  • 105
  • 1
    @Jake Calling `_getData()` from `onPressed` returns `Future` and doesn't change anything in UI – Andrii Turkovskyi Nov 06 '18 at 11:21
  • 1
    as you can see data is being loaded using future: _getData()... how is setState going to bind data again? – fifa chapman Nov 06 '18 at 11:24
  • 2
    setState(() {}); will reload data ? – fifa chapman Nov 06 '18 at 11:32
  • 5
    It will call `build` method again. And `FutureBuilder` get data from `_getData()` method – Andrii Turkovskyi Nov 06 '18 at 11:39
  • 1
    @fifachapman as `_getData()` is within `FutureBuilder`, reloading this segment via `setState(() {});` will in turn reload data – Jake Nov 06 '18 at 11:45
  • 1
    @AndreyTurkovsky any idea about how to refresh screen for same scenario in StatelessWidget? – Md. Sulayman May 25 '21 at 04:37
  • @Md.Sulayman If you want to refresh or change something - you should use StatefulWidget. – Andrii Turkovskyi May 26 '21 at 06:53
  • @AndreyTurkovsky I know that it is easier to do in StatefulWidget. But We are using Flutter HookWidget and updating UI on StatelessWidget. It is kind of preset standard in our team. In our team , we usually try to use less state as much as we can. – Md. Sulayman May 28 '21 at 05:39
  • @Md.Sulayman then you can wrap changeable content in the StreamBuilder or FutureBuilder. For more, then one change - StreamBuilder. And you can refresh or change it each time you emit item in a stream – Andrii Turkovskyi May 31 '21 at 07:51
  • This does not reset the snapshot.hasData or .hasError so when the future is called again it will think it has data and/or error before the future is done executing. – johnw182 Mar 14 '22 at 23:50
47

I did a deep dive into this and it's not that difficult. The builder is properly rebuilt on changing the future (if you trigger the change with setState). Problem is, the hasData and hasError aren't reset until the response is back. But we can use connectionState instead.

final builder = FutureBuilder(
    future: _future,
    builder: (context, snapshot) {
      if (snapshot.connectionState != ConnectionState.done) {
        return _buildLoader();
      }
      if (snapshot.hasError) {
        return _buildError();
      }
      if (snapshot.hasData) {
        return _buildDataView();
      }     
      return _buildNoData();
});

Here's a post on the issue and a linked repo showing the issue and solution: https://www.greycastle.se/reloading-future-with-flutter-futurebuilder/

Almund
  • 5,695
  • 3
  • 31
  • 35
  • 1
    Thanks, I ran into the exact issue as the blog, by just looking at `hasData/hasError/else` in one of my FutureBuilders, instead of also considering `connectionState` initially. – qix Feb 02 '22 at 18:08
  • 4
    I can't believe this is the right answer (but it is). I am getting less and less confident with flutter every day. Why not widget.reload()? why is snapshot not getting reset. Seems like flutter requires a lot of hacky solutions. – johnw182 Mar 14 '22 at 23:54
27

what i did an it worked for me, is to call the future function again in setState(). in your example it will looks like this.

first you assign your _getData() future function to a variable (_myData) with the same return type, after that, you can override it's value in setState() that will rebuild the UI and therefor run the future again.

in code it will looks like this.(from you example):

class _MyHomePageState extends State<MyHomePage> {

Future<List<String>>  _myData = _getData(); //<== (1) here is your Future

@override
      Widget build(BuildContext context) {
        var futureBuilder = new FutureBuilder(
          future: _myData; //<== (2) here you provide the variable (as a future)
          builder: (BuildContext context, AsyncSnapshot snapshot) {
            switch (snapshot.connectionState) {
              case ConnectionState.none:
              case ConnectionState.waiting:
                return new Text('loading...');
              default:
                if (snapshot.hasError)
                  return Column(
                  children: [
                    Icon(Icons.error),
                    Text('Failed to fetch data.'),
                    RaisedButton(
                      child: Text('RETRY'), 
                      onPressed: (){
                        setState(){
                            _myData = _getData(); //<== (3) that will trigger the UI to rebuild an run the Future again
                        }
                      },
                    ),
                  ],
                );
                else
                  return createListView(context, snapshot);
            }
          },
        );

        return new Scaffold(
          appBar: new AppBar(
            title: new Text("Home Page"),
          ),
          body: futureBuilder,
        );
      }
korchix
  • 1,445
  • 1
  • 19
  • 22
1

The FutureBuilder will refresh if a variable is included in the query and setState is invoked with that variable.

Because my query has no needed variables I ran into this problem. To solve this, I created a local dummy variable and then incremented it in setState to cause a refresh.

int _dummy = 0;

Then in set state I did this

              setState(() {
                _dummy++;
              });

The dummy is passed into the query even though it is not used.

            child: FutureBuilder<List<InterviewDetails>>(
                future: dbService.getInterviewDetails(_dummy),

Lastly, I needed to make it wait for the view to close by putting "await"

          ElevatedButton(
            child: const Text('Sign In'),
            onPressed: () async {
              await Navigator.push(
                context,
                MaterialPageRoute(builder: (context) => SignInView()),
              );
              setState(() {
                _dummy++;
              });
            },
          ),

In review:

  • The "sign in" button lets the person sign in.
  • The future builder list needs to show that person who is signed in.
  • wait for the view to close by await and increment the dummy in set state
  • have the future builder use a dummy variable in the query
  • ignore the dummy in the query
Bhikkhu Subhuti
  • 434
  • 2
  • 12
0

You can refresh widget by clicking on FlatButton. The code is below.

class _MyHomePageState extends State<MyHomePage> {

  String display;

  Widget futureBuilder() {

 return new FutureBuilder<String>(builder: (context, snapshot) {
 // if(snapshot.hasData){return new Text(display);}    //does not display updated text
 if (display != null) {
  return new Text(display);
  // return your createListView(context, snapshot);

  }
  return new Text("no data yet");
  });
}

  @override
  Widget build(BuildContext context) {

    return new Scaffold(
      appBar: new AppBar(
        title: new Text("Home Page"),
      ),
      body: Center(
             child: Column(
                 mainAxisAlignment: MainAxisAlignment.center,
                 children: <Widget>[
                        FlatButton(onPressed: () async{
                            result = await _getData();
                            print(result);
                                // Result will be your json response

                            setState(() {
                                display = result; //assign any string value from result to display variable.
                            });
                        },
                        child: new Text("Get Data")
                        ),
                        futureBuilder()
                ],
            ),
        ),

    );
  }

  Future<List<String>> _getData() async {
    var values = new List<String>();

    await new Future.delayed(new Duration(seconds: 5));

    return values;
  }

  Widget createListView(BuildContext context, AsyncSnapshot snapshot) {

  }
}
Jignesh Patel
  • 91
  • 4
  • 6