14

Let's say I have something like this:

return FutureBuilder(
  future: _loadingDeals,
  builder: (BuildContext context, AsyncSnapshot snapshot) {
    return RefreshIndicator(
      onRefresh: _handleRefresh,
        ...
    )
  }
 )

In the _handleRefresh method, I want to programmatically trigger the re-run of the FutureBuilder.

Is there such a thing?

The use case:

When a user pulls down the refreshIndicator, then the _handleRefresh simply makes the FutureBuilder rerun itself.

Edit:

Full code snippet end to end, without the refreshing part. I've switched to using the StreamBuilder, how will the refreshIndicator part fit in all of it?

class DealList extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => new _DealList();
}

class _DealList extends State<DealList> with AutomaticKeepAliveClientMixin {
  // prevents refreshing of tab when switch to
  // Why? https://stackoverflow.com/q/51224420/1757321
  bool get wantKeepAlive => true; 

  final RestDatasource api = new RestDatasource();
  String token;
  StreamController _dealsController;

  @override
  void initState() {
    super.initState();
    _dealsController = new StreamController();
    _loadingDeals();
  }

  _loadingDeals() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();

    this.token = prefs.getString('token');

    final res =
        this.api.checkInterests(this.token).then((interestResponse) async {
      _dealsController.add(interestResponse);
      return interestResponse;
    });
    return res;
  }

  _handleRefresh(data) async {
    SharedPreferences prefs = await SharedPreferences.getInstance();

    final token = prefs.getString('token');
    await this.api.checkInterests(token).then((interestResponse) {
      _dealsController.add(interestResponse);
    });
    return null;
  }

  @override
  Widget build(BuildContext context) {
    super.build(context); // <-- this is with the wantKeepAlive thing
    return StreamBuilder(
      stream: _dealsController.stream,
      builder: (BuildContext context, AsyncSnapshot snapshot) {

        if (snapshot.hasError) {
          ...
        }

        if (snapshot.connectionState != ConnectionState.done) {
          return Center(
            child: CircularProgressIndicator(),
          );
        }

        if (!snapshot.hasData &&
            snapshot.connectionState == ConnectionState.done) {
          return Text('No deals');
        }

        if (snapshot.hasData) {
          return ListView.builder(
                physics: const AlwaysScrollableScrollPhysics(),
                itemCount: snapshot.data['deals'].length,
                itemBuilder: (context, index) {
                  final Map deal = snapshot.data['deals'][index];
                  return ListTile(
                      onTap: () {
                        Navigator.push(
                          context,
                          MaterialPageRoute(
                            builder: (context) => DealsDetailPage(
                                  dealDetail: deal,
                                ),
                          ),
                        );
                      },
                      title: Text(deal['name']),
                      subtitle: Text(deal['expires']),
                    );
                },
              ),
        }
      },
    );
  }
}
KhoPhi
  • 9,660
  • 17
  • 77
  • 128
  • Instead of `Future` what you want is `Stream` as stated by @Feu. – Rémi Rousselet Aug 03 '18 at 00:24
  • 1
    Why not just use `setState`? – Jonah Williams Aug 03 '18 at 00:51
  • @JonahWilliams I'm using `setState` at the moment, but it wasn't working. That might be, because I'm using the `with AutomaticKeepAliveClientMixin` so the whole widget doesn't rebuild on state change. Why? Please see here: https://stackoverflow.com/questions/51224420/flutter-switching-to-tab-reloads-widgets-and-runs-futurebuilder – KhoPhi Aug 03 '18 at 12:06

3 Answers3

12

Why not using a StreamBuilder and a Stream instead of a FutureBuilder?

Something like that...

class _YourWidgetState extends State<YourWidget> {
  StreamController<String> _refreshController;

  ...
  initState() {
    super...
    _refreshController = new StreamController<String>();
   _loadingDeals();
  }

  _loadingDeals() {
    _refreshController.add("");
  }

  _handleRefresh(data) {
    if (x) _refreshController.add("");
  }

  ...
  build(context) {
    ...
    return StreamBuilder(
      stream: _refreshController.stream,
      builder: (BuildContext context, AsyncSnapshot snapshot) {
        return RefreshIndicator(
          onRefresh: _handleRefresh(snapshot.data),
            ...
        )
      }
    );
  }
}

I created a Gist with the Flutter main example using the StreamBuilder, check it out

Feu
  • 5,372
  • 1
  • 31
  • 57
  • Okay. That's some next level code there :p. Will take a deep breath and chew on it. Will let you know how it goes. – KhoPhi Aug 02 '18 at 23:58
  • It appears so, but it's not! Try it and you'll see it works. – Feu Aug 03 '18 at 00:17
  • 1
    I created a Gist with the Flutter main example using the StreamBuilder, check it out: https://gist.github.com/ffeu/e77664dd322089649d9b6f0f8fb04d42 – Feu Aug 03 '18 at 00:19
  • In the above example, you're sending the `snapshot.data` back to the `_handleRefresh` method. Why? And in the `_handleRefresh`, the `if (x) ..` What is the `x`? – KhoPhi Aug 04 '18 at 01:30
  • `snapshot.data`: you don't need to pass it there if you don't need it. `if (x) ..` is the condition you have to trigger the refresh. if there's NO condition, you can remove the `if` and call `_refreshController.add("")` right away. – Feu Aug 04 '18 at 16:27
  • Please I've updated the original question to put in perspective what Ive been able to do so far with the StreamBuilder. How to plug in the refreshIndicator is where I will appreciate assistance. – KhoPhi Aug 04 '18 at 18:27
  • I think I've figured it out. Full code here: https://github.com/seanmavley/refreshindicator-with-streambuilder – KhoPhi Aug 04 '18 at 22:41
5

Using StreamBuilder is a solution, however, to trigger the FutureBuilder programmatically, just call setState, it'll rebuild the Widget.

return RefreshIndicator(
  onRefresh: () {
          setState(() {});
        },
    ...
)
Steev James
  • 2,396
  • 4
  • 18
  • 30
0

I prefer FutureBuilder over StreamBuilder since I am using Firestore for my project and you get billed by reads so my solution was this

    _future??= getMyFuture();

    shouldReload(){
       setState(()=>_future = null)
    }
    
    FutureBuilder(
       future: _future, 
       builder: (context, snapshot){
    return Container();
    },
   )
 

and any user activity that needs you to get new data simply call shouldReload()

Andrea herrera
  • 81
  • 1
  • 11