DEV Community

loading...

Paginate API results with BLoC in Flutter

piedcipher profile image Tirth Patel Originally published at Medium on ・4 min read

Photo by Christin Hume on Unsplash

Hey Everyone 👋, Today we’re going to learn about Pagination in Flutter. Pagination is considered as one of the best practices while loading a large chunk of data from an API. Pagination offers better performance and a jank free experience to the user.

👀 Demo

🧰 We’ll be using,

  • Punk API to get some beers 😋.
  • BLoC patten for state-management.
  • Json Serializable for automatic serialization-deserialization of the API response.

So let’s get started 🍻

🔌 Prerequisites

  • Flutter SDK
  • IDE of choice: VSCode / Intellij Idea / Android Studio
  • Dart & Flutter Plugins for IDE

🔨 Initial Setup

  • After creating a fresh flutter project, add the following dependencies in pubspec.yaml.
  • Delete everything from the main.dart and paste the following content. It has a main method, app routes, and a bloc observer for debugging purposes. Next, we’ll be building the DisplayBeerScreen widget.
  • I’ve organized the project files feature-wise. Here, we have got only a single feature i.e. display freshly brewed beers.

Directory Structure

🎹 Coding

  • Let’s design our BeerRepository. It is going to be a singleton. It contains a method getBeers which requires a page number. Page number will be passed from BeerBloc. I have set the _perPage limit to 10.
  • We’ll be creating a model that will map the API response to Dart class (model) using the fromJson method. The model has 5 fields: id, name, tagline, description, and imageUrl. Don’t forget to run flutter pub run build_runner build command to generate the serialization/deserialization code.
  • Now let’s create business logic component related files & classes: BeerBloc , BeerState , and BeerEvent.
  • There will be 4 states: Initial , Loading , Success & Error. These are self-explanatory.
  • There will be 1 event: BeerFetchEvent.
  • Now, let’s design BeerBloc. It will handle BeerFetchEvent and yield an appropriate state to the UI. It’ll maintain the current page number and isFetching boolean flag to prevent duplicate event requests. BeerRepository is injected via BeerBloc constructor. The value of the page is incremented by 1 after the Success state is yielded.
  • We’ll be yielding BeerInitialState as the Initial State of the UI.
  • When BeerFetchEvent is delegated to the BeerBloc , first it’ll yield the BeerLoadingState. Then, it’ll call the getBeers method of BeerRepository.
  • If the return type of response is of type http.Response then status code of the response is checked. If it’s OK , then we’ll parse the JSON using jsonDecode from dart:convert , and map individual objects to BeerModel. Now, these results can be passed to UI by yielding BeerSuccessState.
  • Otherwise, BeerErrorState is yielded to UI to notify about errors that occurred during API call.
  • At last, comes the UI building part. I’ve kept it clean and simple. There are 3 widgets that compose the entire UI: DisplayBeerScreen, BeerBody, and BeerListItem.
  • DisplayBeerScreen widget displays an AppBar , injects BeerBloc instance via BlocProvider , and renders BeerBody widget.
  • BeerBody widget returns a BlocConsumer to build the reactive UI. BeerBody has 2 fields: a list to hold BeerModel s (_beers), and a ScrollController.
  • listener callback of BlocConsumer conditionally displays the appropriate message using a SnackBar.
  • builder callback of BlocConsumer conditionally builds the widgets. Let’s understand that logic:

Case (1): If the current state is either initial or loading and the value of _beers is empty. In this case, a progress bar is shown. This condition only occurs when the user opens the app for the first time.

Case (2): If the current state is an error and the value of _beers is empty. In this case, IconButton with retry action is shown. This condition only occurs when the user opens the app for the first time and there is some error due to the internet or any other exception. If the user presses the retry then the value of isFetching field of BeerBloc is set to false.

Case (3): If the current state is a success. In this case, beers (API response) yielded by the BLoC are added to the _beers list & isFetching field of BeerBloc is set to false.

  • By default, the builder returns a ListView to render the _beers using the BeerListItem widget. ListView ’s controller is set to a ScrollController instance. This controller has a listener which adds a BeerFetchEvent to BeerBloc (to get the response from the next page of the API) if the user has reached the end of the list-view. It also sets isFetching field of BeerBloc is set to true to prevent duplicate event requests.
  • Finally, there is the BeetListItem widget which shows individual BeerModel data using ExpansionTile , Text , Image.network, and some SizedBox es.

That’s it for this one. Thank you for reading this 💙

You can find the complete source code of this app in this repository.

piedcipher/beer-app

If you find this post useful, press👏 button as many times as you can and share this post with others. You can leave your feedback/suggestions in the comments 💬 below.

For any other issues feel free to reach out to me via Twitter: https://twitter.com/piedcipher

Discussion

pic
Editor guide