I have a RecyclerView that uses a PagingDataAdapter to show its items. On initial load of the entire page, I want to show a Shimmer loading placeholder. However, when I try to do this, the loading placeholder also shows up for a small content change of a single item. This makes the entire screen flicker because it hides the RecyclerView during loading and reshows it once the content change has been loaded. I don't want the loading placeholder to show for a single content change.
I am checking the loading state in the load state listener for the adapter:
addLoadStateListener {
val taskListState = when (it.refresh) { // triggered for both initial load and content change
is LoadState.Loading -> ScheduleViewModel.TaskListState.LOADING
// I want something like if (loadStateIsForInitialLoad()) ScheduleViewModel.TaskListState.LOADING else ScheduleViewModel.TaskListState.PRESENT
is LoadState.Error -> ScheduleViewModel.TaskListState.ERROR
is LoadState.NotLoading ->
if (it.append.endOfPaginationReached && itemCount < 1) {
ScheduleViewModel.TaskListState.EMPTY
} else {
ScheduleViewModel.TaskListState.PRESENT
}
}
scheduleViewModel.setTaskListState(taskListState)
}
The content change goes through the database:
class ScheduleViewModel @Inject constructor(private val taskRepository: TaskRepository) :
ViewModel() {
fun updateDoneState(task: TaskItem) {
viewModelScope.launch(Dispatchers.IO) {
if (task.isDone) {
taskRepository.markUndone(task.id)
} else {
taskRepository.markDone(task.id)
}
}
}
}
Layout:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable name="viewModel" type="ogbe.eva.prompt.ui.schedule.ScheduleViewModel"/>
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.facebook.shimmer.ShimmerFrameLayout
android:id="@+id/shimmerFrameLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:isVisible="@{viewModel.isLoading}">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include layout="@layout/item_task_placeholder"/>
<include layout="@layout/item_task_placeholder"/>
<include layout="@layout/item_task_placeholder"/>
<include layout="@layout/item_task_placeholder"/>
<include layout="@layout/item_task_placeholder"/>
</LinearLayout>
</com.facebook.shimmer.ShimmerFrameLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/task_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/spacing_sm"
app:isVisible="@{viewModel.isPresent}"/>
</FrameLayout>
</layout>
It looks like CombinedLoadStates only has one refresh load state for both initial load and content changes. Is there some other way to distinguish them?
Are you using RemoteMediator
? If so, you can just observe changes to CombinedLoadStates.mediator.refresh
. CombinedLoadStates.refresh
is just a helper which combiens both mediator + source states for "common" use-case.
If you're only using PagingSource
and updating DB / invalidating separately from RemoteMediator
, you can also check against adapter.itemCount
to determine "initial load".
adapter.addLoadStateListener { loadStates ->
when (loadStates.source.refresh) {
is NotLoading -> {
// Always redundantly disable loading spinner here, which assumes doing so
// is stateless.
scheduleViewModel.setTaskListState(
if (it.append.endOfPaginationReached && itemCount < 1) {
ScheduleViewModel.TaskListState.EMPTY
} else {
ScheduleViewModel.TaskListState.PRESENT
}
)
}
is LoadState.Loading -> {
if (adapter.itemCount == 0) {
ScheduleViewModel.TaskListState.LOADING
} else {
ScheduleViewModel.TaskListState.PRESENT
}
}
is LoadState.Error -> ScheduleViewModel.TaskListState.ERROR
}
}
Otherwise, you can also just track this yourself and mix-in with loadStateFlow
, but kind of need to define a bit more precisely what you mean by initial load. For example does configuration change or process death count as initial load or do you just care about non-cached (in memory and db) case?