I'm making an application that displays a list of all GitHub users. For this I use Retrofit
, Paging Library 3
+ Room
. Everything works well, however, after the very first data load, the list jumps to the pageSize
size, this does not happen when you re-enter the application, only on the first load (this is what it looks like). How to fix it?
Dao
@Dao
interface UsersDao {
@Query("SELECT * FROM ${UserListItem.TABLE_NAME}")
fun getUsers(): PagingSource<Int, UserListItem>
}
ViewModel
private const val DEFAULT_PAGE_SIZE = 30
class UserListViewModel(
private val dao: UsersDao,
remoteMediator: UserListRemoteMediator,
) : ViewModel() {
@OptIn(ExperimentalPagingApi::class)
private val pager = Pager(
config = PagingConfig(
pageSize = DEFAULT_PAGE_SIZE,
enablePlaceholders = true,
maxSize = DEFAULT_PAGE_SIZE * 3,
),
remoteMediator = remoteMediator,
pagingSourceFactory = { dao.getUsers() },
)
val users: Flow<PagingData<UserListItem>> = pager.flow.cachedIn(viewModelScope)
}
RemoteMediator
@OptIn(ExperimentalPagingApi::class)
class UserListRemoteMediator(
private val database: UsersDatabase,
private val backend: UsersApi,
private val network: Utils.Network,
) : RemoteMediator<Int, UserListItem>() {
private val dao = database.getUsersDao()
override suspend fun initialize(): InitializeAction {
return if (network.isInternetConnected)
InitializeAction.LAUNCH_INITIAL_REFRESH
else
InitializeAction.SKIP_INITIAL_REFRESH
}
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, UserListItem>
): MediatorResult {
try {
val maxId = when (loadType) {
LoadType.REFRESH -> {
null
}
LoadType.PREPEND -> {
return MediatorResult.Success(endOfPaginationReached = true)
}
LoadType.APPEND -> {
dao.getMaxId()
}
}
val response = backend.getUsers(
after = maxId,
amount = state.config.pageSize
)
if (response.isSuccessful) {
return handleSuccessState(loadType, response)
}
if (response.isRequestsLimitExceeded) {
return handleRequestLimitExceededState(response)
}
return errorWithMessage(message = "Unhandled response code: ${response.code()}")
} catch (e: IOException) {
return MediatorResult.Error(e)
} catch (e: HttpException) {
return MediatorResult.Error(e)
}
}
}
adapter setting method in fragment:
private fun setUpAdapter(binding: FragmentUserListBinding, adapter: UserPagingDataAdapter) {
binding.list.adapter = adapter.withLoadStateHeaderAndFooter(
header = LoadStateAdapter(adapter::retry),
footer = LoadStateAdapter(adapter::retry)
)
viewLifecycleOwner.lifecycleScope.launch {
viewModel.users.collectLatest {
adapter.submitData(it)
}
}
}
fragment layout:
<?xml version="1.0" encoding="utf-8"?>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/user_list_swipe_refresh_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:context=".view.userlist.UserListFragment" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
LoadStateAdapter
class LoadStateAdapter(
private val retry: () -> Unit,
) : LoadStateAdapter<LoadStateViewHolder>() {
override fun onBindViewHolder(holder: LoadStateViewHolder, loadState: LoadState) {
holder.bind(loadState)
}
override fun onCreateViewHolder(parent: ViewGroup, loadState: LoadState): LoadStateViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = LoadStateItemBinding.inflate(layoutInflater, parent, false)
return LoadStateViewHolder(binding, retry)
}
}
LoadStateViewHolder
class LoadStateViewHolder(
private val binding: LoadStateItemBinding,
retry: () -> Unit,
) : RecyclerView.ViewHolder(binding.root) {
init {
binding.retryButton.setOnClickListener { retry() }
}
fun bind(loadState: LoadState) {
if (loadState is LoadState.Error) {
binding.errorMessage.text = loadState.error.localizedMessage
}
binding.errorMessage.isVisible = loadState is LoadState.Error
binding.progressBar.isVisible = loadState is LoadState.Loading
binding.retryButton.isVisible = loadState is LoadState.Error
}
}