Dart

Using RefreshIndicator with Flutter StreamBuilder

I wish I knew this earlier on, so I’ll will make it quick and straightforward. If this is the scenario you find yourself in, then this article might be what you’re looking for.

  • You’re using the ListView.builder to build a list which is incoming from an endpoint (say, REST API)
  • You want the user to, upon swipe down (using the RefreshIndicator), Flutter goes to load new data from server, and update the ListView.builder content accordingly

If that’s what you wish to do, then please, here we go with what we’ll do in this article

  • Retrieve a list of posts from a wordpress endpoint (will use data from blog.khophi.co – which is data that is being used to feed khophi.blog)
  • Upon refresh, go to endpoint to pull more data.

Let’s get going then.

Below is the entire code for this article. I’m not here to waste your time. So go ahead and read the code to see how the StreamBuilder is being used

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:async';
import 'dart:convert';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Basic Project',
      home: new MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  StreamController _postsController;
  final GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>();

  int count = 1;

  Future fetchPost([howMany = 5]) async {
    final response = await http.get(
        'https://blog.khophi.co/wp-json/wp/v2/posts/?per_page=$howMany&context=embed');

    if (response.statusCode == 200) {
      return json.decode(response.body);
    } else {
      throw Exception('Failed to load post');
    }
  }

  loadPosts() async {
    fetchPost().then((res) async {
      _postsController.add(res);
      return res;
    });
  }

  showSnack() {
    return scaffoldKey.currentState.showSnackBar(
      SnackBar(
        content: Text('New content loaded'),
      ),
    );
  }

  Future<Null> _handleRefresh() async {
    count++;
    print(count);
    fetchPost(count * 5).then((res) async {
      _postsController.add(res);
      showSnack();
      return null;
    });
  }

  @override
  void initState() {
    _postsController = new StreamController();
    loadPosts();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      key: scaffoldKey,
      appBar: new AppBar(
        title: new Text('StreamBuilder'),
        actions: <Widget>[
          IconButton(
            tooltip: 'Refresh',
            icon: Icon(Icons.refresh),
            onPressed: _handleRefresh,
          )
        ],
      ),
      body: StreamBuilder(
        stream: _postsController.stream,
        builder: (BuildContext context, AsyncSnapshot snapshot) {
          print('Has error: ${snapshot.hasError}');
          print('Has data: ${snapshot.hasData}');
          print('Snapshot Data ${snapshot.data}');

          if (snapshot.hasError) {
            return Text(snapshot.error);
          }

          if (snapshot.hasData) {
            return Column(
              children: <Widget>[
                Expanded(
                  child: Scrollbar(
                    child: RefreshIndicator(
                      onRefresh: _handleRefresh,
                      child: ListView.builder(
                        physics: const AlwaysScrollableScrollPhysics(),
                        itemCount: snapshot.data.length,
                        itemBuilder: (context, index) {
                          var post = snapshot.data[index];
                          return ListTile(
                            title: Text(post['title']['rendered']),
                            subtitle: Text(post['date']),
                          );
                        },
                      ),
                    ),
                  ),
                ),
              ],
            );
          }

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

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

With the code above, you should get a result looking like this:

Using RefreshIndicator with the StreamBuilder

Our basic Project

flutter create basic && cd basic && flutter run

Delete everything from the lib/main.dart file and replace with the code above.

Boom, you’re done, after you install the Http package from https://pub.dartlang.org/packages/http#-installing-tab- and add to your project

So that’s it?

Yes, that’s it. You wanted more? I didn’t bother going into details because I assume the code is obvious. 

A StreamBuilder basically allows you to fetch new content, and push it through to your ListView.builder which will automatically rebuild itself based on new incoming data. 

I assume to be interested to use Flutter’s StreamBuilder, then you know your way around Flutter and not here for any long ‘distin

In any case, the comment section is below. Lemme know if any issue.

Conclusion

The full source code of the above project is in the RefreshIndicator with StreamBuilder project I have on Github.

If any questions, please leave them in the comments.

Back to top button