Search code examples
androidkotlinandroid-roomandroid-jetpack-composeandroid-paging-3

Android paging RemoteMediator not loading any data


I'm trying to setup Paging 3 for a Jetpack Compose app that gets data from both the Retrofit server and the Room database. I created a RemoteMediator to load the data I want. The very first time I ran the app, it showed the list of items correctly. Any subsequent runs, however, do not show any data even though the data is on the server. I want the data to be shown on the screen.

After checking, it looks like the data got deleted from the Room database and the RemoteMediator load method is not being called anymore. So the item count showing in the composable is 0. I don't know what stopped the load method from being called.

RemoteMediator:

@OptIn(ExperimentalPagingApi::class)
class TaskArchiveRemoteMediator(
    private val clock: Clock,
    private val promptDatabase: PromptDatabase,
    private val promptService: PromptService,
) : RemoteMediator<Int, Task>() {
//    override suspend fun initialize(): InitializeAction {
//        val lastRefreshedAt = promptDatabase.taskDao().getLastArchiveRefreshedAt()
//        return if (lastRefreshedAt?.let { isExpired(it, Instant.now(clock)) } == false) {
//            Log.d("TaskArchiveRemoteMediator", "Should not refresh")
//            InitializeAction.SKIP_INITIAL_REFRESH
//        } else {
//            Log.d("TaskArchiveRemoteMediator", "Should refresh")
//            InitializeAction.LAUNCH_INITIAL_REFRESH
//        }
//    }

    override suspend fun load(loadType: LoadType, state: PagingState<Int, Task>): MediatorResult {
        return try {
            Log.d("TaskArchiveRemoteMediator", "loadType $loadType")
            val loadKey = when (loadType) {
                LoadType.REFRESH -> null
                LoadType.PREPEND -> return MediatorResult.Success(endOfPaginationReached = true)
                LoadType.APPEND -> {
                    val remoteKey = getRemoteKeyForLastItem(state)
                    remoteKey?.nextPageKey
                        ?: return MediatorResult.Success(endOfPaginationReached = true)
                }
            }
            val limit = if (loadType == LoadType.REFRESH) {
                state.config.initialLoadSize
            } else {
                state.config.pageSize
            }

            val page = getTaskPage(loadKey, limit)
            Log.d("TaskArchiveRemoteMediator", "page: $page")

            promptDatabase.withTransaction {
                if (loadType == LoadType.REFRESH) {
                    promptDatabase.taskDao().deleteArchive()
                    promptDatabase.taskRemoteKeyDao().deleteAll()
                }

                page.tasks.lastOrNull()?.let {
                    promptDatabase.taskRemoteKeyDao().insert(TaskRemoteKey(it.id, page.nextCursor))
                }

                val tasks = page.tasks.map { it.toTask(Instant.now(clock)) }
                Log.d("TaskArchiveRemoteMediator", "tasks: $tasks")
                promptDatabase.taskDao().insertAll(tasks)
            }

            MediatorResult.Success(endOfPaginationReached = page.nextCursor == null)
        } catch (e: TaskArchivePagingException) {
            Log.e("TaskArchiveRemoteMediator", "archive load error", e)
            MediatorResult.Error(e)
        } catch (e: IOException) {
            Log.e("TaskArchiveRemoteMediator", "archive load error", e)
            MediatorResult.Error(e)
        } catch (e: HttpException) {
            Log.e("TaskArchiveRemoteMediator", "archive load error", e)
            MediatorResult.Error(e)
        }
    }

    private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, Task>) =
        state.lastItemOrNull()?.id?.let { promptDatabase.taskRemoteKeyDao().getById(it) }

    private suspend fun getTaskPage(after: String?, size: Int): TaskPage {
        val response = promptService.getArchive(after, size)
        if (!response.isSuccessful) {
            throw TaskArchivePagingException(response.message())
        }

        return response.body() ?: throw TaskArchivePagingException("Server response has empty body")
    }
}

Repository:

class TaskArchiveRepository @Inject constructor(
    private val clock: Clock,
    private val promptDatabase: PromptDatabase,
    private val promptService: PromptService,
) {
    @OptIn(ExperimentalPagingApi::class)
    fun getArchive(pageSize: Int) =
        Pager(
            PagingConfig(pageSize),
            remoteMediator = TaskArchiveRemoteMediator(clock, promptDatabase, promptService),
        ) {
            promptDatabase.taskDao().getArchive()
        }.flow
}

Composable:

@Composable
fun TaskArchiveScreen(
    setTopBarState: (TopBarState) -> Unit = {},
    navigateToTaskDetails: (String) -> Unit = {},
    taskArchiveViewModel: TaskArchiveViewModel = hiltViewModel(),
) {
    val taskItems = rememberWithLifecycle(taskArchiveViewModel.archive).collectAsLazyPagingItems()
    val hasData = taskItems.loadState.refresh is LoadState.NotLoading && taskItems.itemCount > 0
    Log.d("TaskArchiveScreen", "loadState: ${taskItems.loadState}, itemCount: ${taskItems.itemCount}")
    val overflowActions = if (hasData) {
        val refreshAction = ActionState(
            icon = painterResource(R.drawable.ic_baseline_refresh_24),
            title = stringResource(R.string.refresh_action),
            onClick = taskItems::refresh,
        )
        listOf(refreshAction)
    } else {
        emptyList()
    }
    setTopBarState(
        TopBarState(
            title = stringResource(R.string.task_archive_title),
            navIconType = NavIconType.Menu,
            overflowActions = overflowActions,
        )
    )
    when {
        taskItems.loadState.refresh is LoadState.Loading -> TaskListLoadingContent()
        taskItems.loadState.refresh is LoadState.Error ->
            LoadFailureContent(
                loadFailureMessage = stringResource(R.string.load_task_archive_failure_message),
                reload = taskItems::retry,
            )
        hasData ->
            TaskArchiveDataContent(
                taskItems = taskItems,
                navigateToTaskDetails = navigateToTaskDetails,
            )
        else -> TaskArchiveEmptyContent()
    }
}

How do I get my paged items to load?


Solution

  • Turns out the data coming from the server was malformed :(