1 min read

Pagination

The Poq Pagination SDK is a wrapper around Android jetpack Paging 3. This SDK simplifies the implementation of the recommended Poq pagination solution which only supports pagination over the network and not over a database. The Poq pagination solution is URL based and your backend needs to return the pagination matching the network model NetworkPagination.

Start using the Pagination SDK

The platform module includes the Pagination SDK module, but if you need to include it, add it to your build.gradle:

implementation ("com.poqstudio:pagination:$VERSION_NAME")

Koin modules

Poq Pagination SDK dependencies are injected using Koin. The SDK provides the default dependencies definitions via this Koin module:

  • paginationModule

Overview

Pagination general architecture. Coloured boxes are the classes provided by the SDK.
Pagination general architecture. Coloured boxes are the classes provided by the SDK.

  • PaginationApiService is the interface that your ApiService needs to implement which has two methods: getFirstPage and getPageByUrl.
  • PoqPagingSource is the implementation of BasePagingSource, it is a generic implementation of RxPagingSource.
  • PoqPagingSourceFactory is the implementation of BasePagingSourceFactory, used to create PoqPagingSource. It has 3 generics parameters:
    • PaginatedData: the network model of the data that will be paginated.
    • OutPut: the network model which PaginationApiService returns. It needs to implement PaginationResponse and it contains the PaginatedData, the NetworkPagination and any other data you require outside the pagination.
    • Parameters: the parameters model, such as an id, which will be forwarded to the PaginationApiService.
  • BasePagingDataAdapter is the adapter responsible for displaying the PagingData.
  • BaseLoadStateAdapter, and its factory BaseLoadStateAdapterFactory, is the adapter responsible for displaying the loading and error states when fetching new pages.

How to add a new pagination implementation

To implement a pagination using the Poq Pagination SDK you need to:

  1. Create a network PaginatedData model that represents the items that are going to be paginated, such as NetworkItem.
  2. Create a network OutPut model implementing PaginationResponse<NetworkItem>, such as NetworkOutput.
  3. Create your API service implementing PaginationApiService.
  4. Inside your Repository implementation, create the Pager object with the BasePagingSource created with BasePagingSourceFactory:
    class Repository(
    private val basePagingSourceFactory: BasePagingSourceFactory<NetworkItem, NetworkFeature, Parameters>,
    private val networkToDomainFeatureItemMapper: Mapper<FeatureItem, NetworkFeatureItem>,
    private val networkToDomainFeatureDataMapper: Mapper<FeatureData, NetworkFeature>,
    ) {
    fun getFeatureModel(): FeatureModel {
    val basePagingSource = basePagingSourceFactory.create(
    mapHeaders(),
    parameters
    )
    return FeatureModel(
    itemList = Pager(
    config = PagingConfig(pageSize = 48), // Recommended page size
    pagingSourceFactory = { basePagingSource }
    ).observable.map { pagingData ->
    pagingData.map(networkToDomainFeatureItemMapper::map)
    },
    extraData = basePagingSource.result.map(networkToDomainFeatureDataMapper::map),
    )
    }
    }
  5. Inside your ViewModel implementation, get your FeatureModel, subscribe to the itemList observable using cachedIn(viewModelScope) and your extraData observable if you have it:
    getFeatureModel().apply {
    compositeDisposable.addAll(
    itemListList
    .cachedIn(viewModelScope)
    .subscribe{...},
    extraData.subscribe{...}
    )
    }
  6. Inside your Activity/Fragment, create a ConcatAdapter from your implementation of BasePagingDataAdapter and BaseLoadStateAdapter:
    val adapter = FeaturePagingDataAdapter(...)
    val loadStateAdapter = get<BaseLoadStateAdapterFactory>().create { adapter.retry() }
    recyclerView.adapter = adapter.withLoadStateFooter(loadStateAdapter)
  7. To display the empty, loading and error states of the first page:
    • Use the provided methods in BasePagingDataAdapter to attach/detach a CombinedLoadStates listener. Forward the CombinedLoadStates and a snapshot of the current list to your ViewModel:
      private var loadStateListener: ((CombinedLoadStates) -> Unit)? = null
      fun attachListener(){
      loadStateListener = { loadStates ->
      viewModel.submitPaginationData(
      loadStates,
      adapter.snapshot().items
      )
      }
      loadStateListener?.let { adapter.attachLoadStateListener(it) }
      }
      fun detachListener(){
      loadStateListener?.let { adapter.detachLoadStateListener(it) }
      }
    • In your ViewModel, expose LiveDatas for the different states from the forwarded data:
      val isEmptyContent = MutableLiveData<Boolean>()
      val isLoadingFirstPage = MutableLiveData<Boolean>()
      val isError = MutableLiveData<Boolean>()
      fun submitPaginationData(
      loadStates: CombinedLoadStates,
      datasetInAdapter: List<FeatureItem>,
      ) {
      isLoadingFirstPage.postValue(loadStates.refresh is LoadState.Loading)
      isError.postValue(loadStates.refresh is LoadState.Error)
      isEmptyContent.postValue(datasetInAdapter.isEmpty())
      }
If your network model is not accessed directly, like NetworkReviewDetailList, remember to add @Keep to prevent R8 to minify your class.