(GitHub repo linked at the end of the article.)
A lot of times, you’ll need to load a long list of items from the web. In such cases, you wouldn’t want to load the entire list at once. Most likely, you would need to use GET
to fetch the content lazily, that is, one page at a time. Also, you don’t need to get more until the user scrolls towards the bottom of the list. Yeah, something like this:
When I was developing my last app, I was in such a position, and looked everywhere for a simple solution. However, all the solutions I found involves some sort of advanced state management tools like the NotificationListener
or ScrollController
. By far the simplest solutions I found are AbdulRahman AlHamali’s FutureBuilder
solution and a package called lazy_load_scrollview.
I wasn’t satisfied. My app was very simple and I intended to keep it that way. Isn’t there a simpler way to do this, using just the most basic StatefulWidget
? I wondered… And the answer is no, you have to bear with it.
Just kidding… The answer is always yes, and I’m about to show you how.
The basic idea is to utilize the lazy loading behavior of the amazing ListView.builder()
, and trigger the asynchronous fetching/loading when user hits the bottom, that is, when the builder calls the itemBuilder
on the last item. In fact, the official Flutter Get Started App tutorial already uses the lazy loading mechanism to get an infinite list of word pairs. There are only two problems we have to deal with on top of that strategy:
- The http
GET
is asynchronous and so are a lot of other loading actions. - (Optional) We need to stop loading when the list comes to and end.
Now let me walk you through step by step. We’ll start with an app that looks a lot like Get Started App from Flutter project: we have a StatefulWidget
and its State
that displays a list of word pairs. You may download the starting project or clone the tutorial repo and git checkout start
.
Inside lib/list_screen.dart
you’ll find a StatefulWidget
and its State
. Right now it simply displays a fixed list with a single item.
The _ItemFetcher
class is here to act as a dummy http agent. Notice that its fetch()
method is an async
block that has a delayed future in it. The asynchronous behavior is the tricky element in our example: you need to make sure that it only gets called once for each page.
So what we gotta do now is to use the ListView.builder()
’s itemBuilder
callback. We deliberately set the itemCount
to _pairList.length + 1
so that we can:
- Display a loading indicator, and
- Trigger
fetch()
when the list is near its bottom.
But be careful not to call fetch()
when one is already underway, so we use a _isLoading
flag to indicate if a fetch()
is already triggered. The loading logic is implemented in the _loadMore()
method. It first sets _isLoading
to true
and then fires off the fetch()
. A then()
block is used to deal with the future’s return.
In addition, we use a _hasMore
flag to indicate that there’s no more items to fetch. When _hasMore
is false
we will set itemCount
to _pairList.length
so that the loading indicator won’t be shown anymore and no more fetch()
would be triggered.
Putting these all together, the final code for the State
looks like this:
Voila, a simple solution that doesn’t go much further than the Get Started App, yet it satisfies everything we want! As you can see, this app doesn’t go far beyond the concepts of the Flutter getting started tutorial, avoiding some advanced concepts like the ScrollController
or the FutureBuilder
, and is suited for any beginner!