First off, I’m not a Flutter expert in any way, so many decisions illustrated here may be naive or inefficient or even plain ugly in eyes of true Flutter aficionado. However, we all started with something, so let’s get our hands dirty.

For the sake of exercise, let’s consider a very typical task of lazy loading a collection of data items from the network and displaying them in a list on the mobile device. Data can be fetched from the back end in ‘pages’ and that leads us for a few implications:

If we need to display items #2 and #3 and they happen to belong to the same ‘page’, we certainly do not want to make two exact same network calls If we have fetched data for a particular item, displayed it and then this item goes out of view (or perhaps its Android view have been destroyed/recycled by a RecyclerView) and then in a short period of time we need to display it again, we don’t want to make another network call. Yes, that is we want to have some sort of caching.

Shall we start? Assume the backend API is as simple as just one endpoint for fetching a page of data:

That’s pretty trivial and the only interesting thing here is that the single method of our API is explicitly declared as asynchronous. Now let’s get a product and a page of products models that can inflate themselves out of response JSON:

... and …

So, now we have all the needful to retrieve data in pages, but should the rest of our app be aware of this concept? In most cases the answer is definite No, we’d rather have a layer of abstraction to get a product by index, and let this figure out wether or not a network call has to be made or we have some locally cached data available for immediate re-use. We’ll call this layer Repository; let’s first define interfaces for the Repository itself and I promise we are getting closer to something interesting, flutter-ish I’d say:

and Cache:

Now, define the logic for abstracting out pagination and caching. It’s pretty simple — whenever a product is requested by another part of the app, we lookup in cache, if it’s there, we just serve it. If it’s not there AND we have not requested the corresponding page from the backend, we do request it and return a Future to the client. And when a page of data gets back from the server, we complete all the pending Futures that can be resolved by that page. Sound complicated? Implementation shows that it’s as simple as promised:

We are all set to get a product that we need whenever we’d like to display it; but wait! Our repository returns not a product, but its future, so how are we supposed to render it on the screen? Perhaps, listen to its stream just like in the code above when waiting for the server response? That’s an option, but luckily we have even better option provided by Flutter, called FutureBuilder that abstracts all these details out for us:

Add salt and pepper and voila, here we go: