2

I have paged list to which there is a possibility to apply some filter. After user applies the filters, recyclerview should display 0 items with the loading footer until it loads the first items and fire a LoadResul.Error in case there is nothing to show.

The footer works fine but you can still see data that was there before calling .invalidate() and you'll see that data until it gets a new response from backend (which can take some time if you're having bad internet). And in case of error, the old items will still be there and the error footer will appear.

Any idea on how to achieve that ?

I am also using the library with RxJava so here is the DataSource code:


    override fun getRefreshKey(state: PagingState<Int, Event>): Int = 1

    override fun loadSingle(params: LoadParams<Int>): Single<LoadResult<Int, Event>> {
        val key = params.key ?: 1

        return repository.getCityEvents(
            cityId = globalData.currentCityId,
            start = selectedDates?.start?.toApiFormat(),
            end = selectedDates?.end?.toApiFormat(),
            pageNo = key,
            categories = categoryFilters
        )
            .map { it.content }
            .map { toLoadResult(it, params) }
            .onErrorReturn { LoadResult.Error(it) }
    }

    private fun toLoadResult(data: List<Event>, params: LoadParams<Int>): LoadResult<Int, Event> {
        val currentKey = params.key ?: 1

        return if (data.isEmpty() && currentKey == 1)
            LoadResult.Error(NoEventsException())
        else
            LoadResult.Page(
                data = data,
                prevKey = null,
                nextKey = if (data.size < params.loadSize) null else currentKey + 1
            )
    }
MONK
  • 179
  • 2
  • 15

1 Answers1

3

First of all you need to add LoadStateAdapter to your PagingDataAdapter like this:

item_paging_loading.xml:

<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="12dp">

    <ProgressBar
        android:id="@+id/progressbar"
        android:layout_width="32dp"
        android:layout_height="32dp"
        android:layout_gravity="center_horizontal" />

    <TextView
        android:id="@+id/textError"
        style="@style/TextSmall"
        android:layout_gravity="center"
        android:gravity="center"
        android:visibility="gone"
        tools:text="Some Error Occurred"
        tools:visibility="visible" />

    <TextView
        android:id="@+id/buttonRetry"
        style="@style/TextMedium"
        android:layout_gravity="center"
        android:background="?selectableItemBackground"
        android:padding="@dimen/_5dp"
        android:text="@string/retry"
        android:textColor="@color/colorAccent" />
</androidx.appcompat.widget.LinearLayoutCompat>

PaginationLoadStateAdapter.kt

class PaginationLoadStateAdapter(
    val retry: () -> Unit
) : LoadStateAdapter<PaginationLoadStateAdapter.PassengersLoadStateViewHolder>() {

    inner class PassengersLoadStateViewHolder(
        private val binding: ItemPagingLoadingBinding,
        private val retry: () -> Unit
    ) : RecyclerView.ViewHolder(binding.root) {
        fun bind(loadState: LoadState) {
            if (loadState is LoadState.Error) {
                binding.textError.text = loadState.error.localizedMessage
            }
            binding.progressbar.visible(loadState is LoadState.Loading)
            binding.buttonRetry.visible(loadState is LoadState.Error)
            binding.textError.visible(loadState is LoadState.Error)
            binding.buttonRetry.setOnClickListener {
                retry()
            }
        }
    }

    override fun onBindViewHolder(
        holder: PassengersLoadStateViewHolder,
        loadState: LoadState
    ) {
        holder.bind(loadState)
    }

    override fun onCreateViewHolder(
        parent: ViewGroup,
        loadState: LoadState
    ) = PassengersLoadStateViewHolder(
        ItemPagingLoadingBinding.inflate(
            LayoutInflater.from(parent.context), parent, false
        ),
        retry
    )
}

An extension function for add header and footer to your PagingDataAdapter

fun <T : Any, V : RecyclerView.ViewHolder> PagingDataAdapter<T, V>.withLoadStateAdapters(
    header: LoadStateAdapter<*>,
    footer: LoadStateAdapter<*>
): ConcatAdapter {
    addLoadStateListener { loadStates ->
        header.loadState = loadStates.refresh
        footer.loadState = loadStates.append
    }

    return ConcatAdapter(header, this, footer)
}

And you need to set header and footer like this to your RV:

binding.list.adapter = pagingDataAdapter.withLoadStateAdapters(
                header = PaginationLoadStateAdapter { adapterSimpleOffers.retry() },
                footer = PaginationLoadStateAdapter { adapterSimpleOffers.retry() }
            )

After user applies the filters you can to submit empty data for show 0 items, then load your paging again.

pagingDataAdapter.submitData(lifecycle, PagingData.empty())
loadData()//load your data