Flutter Infinite List Tutorial with “flutter_bloc”

Last time, we made a simple login flow using the bloc and flutter_bloc packages. If you haven’t checked out that tutorial, you can find it here. In addition, if you want to stay up-to-date with all the bloc package tutorials, you can check out the new bloc package website.

In this tutorial, we’re going to be implementing an app which fetches data over the network and loads it as a user scrolls; it will look something like this:

A demo of the finished product

Let’s get started!

We’ll start off by creating a brand new Flutter project

flutter create flutter_infinite_list

We can then go ahead and replace the contents of pubspec.yaml with

and then install all of our dependencies

flutter packages get

For this demo application, we’ll be using jsonplaceholder as our data source. If you’re not familiar with jsonplaceholder, it’s an online REST API which serves fake data; it’s very useful for building prototypes.

Open a new tab in your browser and visit https://jsonplaceholder.typicode.com/posts?_start=0&_limit=2 and you’ll see what the data we’ll be working with looks like.

Notice, in our url we specified the start and limit as query parameters to the GET request.

Great, now that we know what our data is going to look like, let’s create the model.

Create post.dart and let’s get to work creating the model of our Post object.

Post is just a class with an id, title, and body. We can override the toString function in order to have a custom string representation of our Post for later. In addition, we extend Equatable so that we can compare Posts; by default, the equality operator returns true if and only if this and other are the same object (docs).

Now that we have our Post object model, let’s start working on the Business Logic Component (bloc).

Before we dive into the implementation, we need to define what our PostBloc is going to be doing.

At a high level, it will be responding to user input (scrolling) and fetching more posts in order for the presentation layer to display them. Let’s start by creating our Event.

Our PostBloc will only be responding to a single event; Fetch which will be dispatched by the presentation layer whenever it needs more Posts to present. Since our Fetch event is a type of PostEvent we can create post_event.dart and implement the event like so.

Again, we are overriding toString for an easier to read string representation of our event. Again, we need to extend Equatable so that we can compare instances for equality.

To recap, our PostBloc will be receiving PostEvents and converting them to PostStates. We have defined all of our PostEvents (Fetch) so next let’s define our PostState.

Our presentation layer will need to have several pieces of information in order to properly lay itself out:

  • PostUninitialized- will tell the presentation layer it needs to render a loading indicator while the initial batch of posts are loaded
  • PostLoaded- will tell the presentation layer it has content to render
  • PostError- will tell the presentation layer that an error has occurred while fetching posts

We can now create post_state.dart and implement it like so.

*We implemented copyWith so that we can copy an instance of PostState and update zero or more properties conveniently (this will come in handy later ).

Now that we have our Events and States implemented, we can create our PostBloc.

For simplicity, our PostBloc will have a direct dependency on an http client; however, in a production application you might want instead inject an api client and use the repository pattern (docs).

Let’s create post_bloc.dart and create our empty PostBloc.

*Note that just from the class declaration we can tell that our PostBloc will be taking PostEvents as input and outputting PostStates.

We can start by implementing initialState which will be the state of our PostBloc before any events have been dispatched.

Next, we need to implement mapEventToState which will be fired every time an Event is dispatched.

Our PostBloc will yield whenever there is a new state because it returns a Stream<PostState>. Check out the bloc library documentation for more information about Streams and other core concepts.

Now every time a PostEvent is dispatched, if it is a Fetch event and if our current state has not reached the max, our PostBloc will fetch the next 20 posts.

The API will return an empty array if we try to fetch beyond the max posts (100) so if we get back an empty array, our bloc will yield the currentState except we will set hasReachedMax to true.

If we cannot retrieve the posts, we throw an exception and yield PostError.

If we can retrieve the posts, we return PostLoaded which takes the entire list of posts.

One optimization we can make is to debounce the Events in order to prevent spamming our API unnecessarily. We can do this by overriding the transform method in our PostBloc.

*Overriding transform allows us to transform the Stream<Event> before mapEventToState is called. This allows for operations like distinct(), debounce(), etc... to be applied.

Our finished PostBloc should now look like this:

Great! Now that we’ve finished implementing the business logic all that’s left to do is implement the presentation layer.

In our main.dart we can start by implementing our main function and calling runApp to render our root widget.

Next, we need to implement our HomePage widget which will present our posts and hook up to our PostBloc.

HomePage is a StatefulWidget because it will need to maintain a ScrollController as well as the PostBloc. In the State, we create our instances of ScrollController and PostBloc and in the constructor, we add a listener to our ScrollController so that we can respond to scroll events. In addition, in our constructor, we need to dispatch a Fetch event so that when the app loads, it requests the initial batch of Posts.

Moving along, our build method returns a BlocBuilder. BlocBuilder is a Flutter widget from the flutter_bloc package which handles building a widget in response to new bloc states. Anytime our PostBloc state changes, our builder function will be called with the new PostState.

Also, we need to remember to clean up after ourselves and dispose our bloc when our StatefulWidget is disposed.

Whenever the user scrolls, we calculate how far away from the bottom of the page the user is and if the distance is ≤ our _scrollThreshold we dispatch a Fetch event in order to load more posts.

Next, we need to implement our BottomLoader widget which will indicate to the user that we are loading more posts.

Lastly, we need to implement our PostWidget which will render an individual Post.

At this point, we should be able to run our app and everything should work; however, there’s one more thing we can do.

One added bonus of using the bloc library is that we can have access to all Transitions in one place.

The change from one state to another is called a Transition. A Transition consists of the current state, the event, and the next state.

Even though in this application we only have one bloc, it's fairly common in larger applications to have many blocs managing different parts of the application's state.

If we want to be able to do something in response to all Transitions we can simply create our own BlocDelegate.

All we need to do is extend BlocDelegate and override the onTransition method.

In order to tell Bloc to use our SimpleBlocDelegate, we just need to tweak our main function.

Now when we run our application, every time a Bloc Transition occurs we can see the transition printed to the console. In practice, you can create different BlocDelegates and because every state change is recorded, we are able to very easily instrument our applications and track all user interactions and state changes in one place!

That’s all there is to it! We’ve now successfully implemented an infinite list in flutter using the bloc and flutter_bloc packages and we’ve successfully separated our presentation layer from our business logic.

Our HomePage has no idea where the Posts are coming from or how they are being retrieved. Conversely, our PostBloc has no idea how the State is being rendered, it simply converts events into states.

You can find the full source for this project here.

If you enjoyed this exercise as much as I did you can support me by ⭐️the repository, or 👏 for this story.