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:
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
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.
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
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
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
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.
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
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).
post_bloc.dart and create our empty
*Note that just from the class declaration we can tell that our
PostBloc will be taking
PostEvents as input and outputting
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.
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
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
transform allows us to transform the
mapEventToState is called. This allows for operations like
debounce(), etc... to be applied.
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.
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
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
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 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
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
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
All we need to do is extend
BlocDelegate and override the
In order to tell Bloc to use our
SimpleBlocDelegate, we just need to tweak our
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.
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.
The latest Tweets from Felix Angelov (@felangelov). software engineer by day, software engineer by night. BMW tech…twitter.com