How To Lazy Load Large List From HTTP REST API With Pagination In Flutter
Lazy loading a large list with pagination from a REST API in Flutter is bit tricky because of the way ListView behaves in Flutter. As long as you are clear on how a ListView, it’s index and state behaves, then working with ListView in Flutter is a breeze. In this article let’s see how to lazy load a large list in Flutter without any additional third party ListView plugins.
The entire Activity can be split into multiple steps. Follow the bellow steps to create a lazy loading list. In this example , I using an API from Random Users Website.
Create a StatefulWidget
The first step is to create a stateful widget with a Scroll Controller to track the scroll position in the device. Based on the scroll position,
we would be loading the next set of data.
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
class Home extends StatefulWidget {
@override
State<StatefulWidget> createState() => new HomeState();
}
class HomeState extends State<Home> {
static int page = 0;
ScrollController _sc = new ScrollController();
bool isLoading = false;
List users = new List();
final dio = new Dio();
@override
void initState() {
this._getMoreData(page);
super.initState();
_sc.addListener(() {
if (_sc.position.pixels ==
_sc.position.maxScrollExtent) {
_getMoreData(page);
}
});
}
@override
void dispose() {
_sc.dispose();
super.dispose();
}
....
....
....
}
Lazy Load Large List
In this step we would be loading the data from REST API. I am using Dio for http requests, you can also use the the standard http plugin for this.
....
....
final dio = new Dio();
....
....
void _getMoreData(int index) async {
if (!isLoading) {
setState(() {
isLoading = true;
});
var url = "https://randomuser.me/api/?page=" +
index.toString() +
"&results=20&seed=abc";
print(url);
final response = await dio.get(url);
List tList = new List();
for (int i = 0; i < response.data['results'].length; i++) {
tList.add(response.data['results'][i]);
}
setState(() {
isLoading = false;
users.addAll(tList);
page++;
});
}
}
Creating The List
The next step is to create the List and bind the list with data.
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Lazy Load Large List"),
),
body: Container(
child: _buildList(),
),
resizeToAvoidBottomPadding: false,
);
}
Widget _buildList() {
return ListView.builder(
itemCount: users.length + 1, // Add one more item for progress indicator
padding: EdgeInsets.symmetric(vertical: 8.0),
itemBuilder: (BuildContext context, int index) {
if (index == users.length) {
return _buildProgressIndicator();
} else {
return new ListTile(
leading: CircleAvatar(
radius: 30.0,
backgroundImage: NetworkImage(
users[index]['picture']['large'],
),
),
title: Text((users[index]['name']['first'])),
subtitle: Text((users[index]['email'])),
);
}
},
controller: _sc,
);
}
Other Items To Note
Along with the text data, an avatar image is also added to list item. Whenever the scroll position reaches the bottom, the next set of data is requested by increasing the page index by one. In addition to that, a loading indicator is shown while the data request to REST API is performed.
Also ensure that the scroll controller is disposed properly.
The bellow is the entire code that is been is been explained in this article. In addition to this code, you may have to hook this to the main.dart
or to your main function to load this class.
Full Code
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
class Home extends StatefulWidget {
@override
State<StatefulWidget> createState() => new HomeState();
}
class HomeState extends State<Home> {
static int page = 0;
ScrollController _sc = new ScrollController();
bool isLoading = false;
List users = new List();
final dio = new Dio();
@override
void initState() {
this._getMoreData(page);
super.initState();
_sc.addListener(() {
if (_sc.position.pixels ==
_sc.position.maxScrollExtent) {
_getMoreData(page);
}
});
}
@override
void dispose() {
_sc.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Lazy Load Large List"),
),
body: Container(
child: _buildList(),
),
resizeToAvoidBottomPadding: false,
);
}
Widget _buildList() {
return ListView.builder(
itemCount: users.length + 1, // Add one more item for progress indicator
padding: EdgeInsets.symmetric(vertical: 8.0),
itemBuilder: (BuildContext context, int index) {
if (index == users.length) {
return _buildProgressIndicator();
} else {
return new ListTile(
leading: CircleAvatar(
radius: 30.0,
backgroundImage: NetworkImage(
users[index]['picture']['large'],
),
),
title: Text((users[index]['name']['first'])),
subtitle: Text((users[index]['email'])),
);
}
},
controller: _sc,
);
}
void _getMoreData(int index) async {
if (!isLoading) {
setState(() {
isLoading = true;
});
var url = "https://randomuser.me/api/?page=" +
index.toString() +
"&results=20&seed=abc";
print(url);
final response = await dio.get(url);
List tList = new List();
for (int i = 0; i < response.data['results'].length; i++) {
tList.add(response.data['results'][i]);
}
setState(() {
isLoading = false;
users.addAll(tList);
page++;
});
}
}
Widget _buildProgressIndicator() {
return new Padding(
padding: const EdgeInsets.all(8.0),
child: new Center(
child: new Opacity(
opacity: isLoading ? 1.0 : 00,
child: new CircularProgressIndicator(),
),
),
);
}
}
The bellow is the screenshot from simulator based on the above code.
Leave a comment