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: