Home > other >  In Jetpack Compose, how do I specifically handle errors in paging3 and display them on ui?
In Jetpack Compose, how do I specifically handle errors in paging3 and display them on ui?

Time:05-24

In my case, I'm requesting a GitHub API, if I request a lot of data, I might get a 403 error, while if I run out of requested data, GitHub will return me a 422 Unprocessable Entity. So I want to prompt the user on the UI based on this whether it is rate limited by the API or there is no new data.

    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, EventWithRepoInfo> {
        val position = params.key ?: GITHUB_STARTING_PAGE_INDEX
        return try {
            val receivedEvents = network.receivedEvents(token, network.getUserInfo(token).login, position, params.loadSize)
            val eventWithRepoInfo: MutableList<EventWithRepoInfo> = mutableListOf()
            val nextKey = if (receivedEvents.isEmpty()) {
                null
            } else {
                position   (params.loadSize / NETWORK_PAGE_SIZE)
            }
            nextKey?.let {
                receivedEvents.forEach {
                    eventWithRepoInfo.add(EventWithRepoInfo(it, ktorClient.getRepoInfo(token, it.repo.url)))
                }
            }
            LoadResult.Page(
                data = eventWithRepoInfo,
                prevKey = if (position == GITHUB_STARTING_PAGE_INDEX) null else position - 1,
                nextKey = nextKey
            )
        } catch (exception: IOException) {
            exception.printStackTrace()
            return LoadResult.Error(exception)
        } catch (exception: HttpException) {
            exception.printStackTrace()
            return LoadResult.Error(exception)
        }
    }

So I want to judge whether it is 403 or 422 in this place. If it is 422, it will show that there is no more data, and 403 will prompt the user that the api is rate-limited.

    LazyColumn {
        events.apply {
            when {
                loadState.append is LoadState.Loading -> {
                    item {
                            Text("loading")
                        }
                    }
                }
                // I want to show different things here depending on different error codes
                loadState.append is LoadState.Error -> {
                    item {
                        LargeButton(onClick = { events.retry() }) {
                            Text("loading error, retry.")
                        }
                    }
                }
            }
        }
    }

CodePudding user response:

so multiple things here, if you really want to use the power of paging (and so, load item only when the user scroll), you need to use the items method provided by the LazyColumn, that take in argument a LazyPagingItems<T>

Firstly, you should retrieve the PagingData flow from the Pager.

val githubPager = Pager(...)
val pagingItems : Flow<PagingData<MyCustomDto>> = githubPager.flow

Once you get the flow of paging data, ideally you should cache it inside you're view model scope, and then expose it to you're view

val cachedPagingItems = pagingItems.cachedIn(viewModelScope)

Inside you're composable, you can now then collect the pagingItems, transmit the error to you're view model, and show the paging data inside you're LazyColumn

I've made a small extension function to perform the remember operation, and expose a retry block directly :

@Composable
fun <T : Any> Flow<PagingData<T>>.collectAndHandleState(
    handleLoadStates: (LoadStates, () -> Unit) -> Job
): LazyPagingItems<T> {
    val lazyPagingItem = collectAsLazyPagingItems()

    // Handle the different load state event
    val pagingLoadStates = lazyPagingItem.loadState.mediator ?: lazyPagingItem.loadState.source
    LaunchedEffect(pagingLoadStates) {
        handleLoadStates(pagingLoadStates)
    }

    return lazyPagingItem
}

To use this extension function, do inside you're composable :

// Get our paging items
val pagingItems = viewModel.cachedPagingItems.collectAndHandleState(viewModel::handleLoadState)
LazyColumn {
    item {
        // Display an error if any, or any informative message here, coming from an UiState exposed by you're view model
    } 

    // Display the items
    items(pagingItems) { pagedItem ->
        // The composable you want to display
    }


}

And inside you're view model, a simple function :

fun handleLoadState(loadStates: LoadStates, retry: () -> Unit): Job {
    // Handle the different load state, find the error or loading ones and update you're ui state
    // To rapidly find an error you can do : 
    val errorLoadState = arrayOf(
        loadStates.append,
        loadStates.prepend,
        loadStates.refresh
    ).filterIsInstance(LoadState.Error::class.java).firstOrNull()
    // And then check the error by getting the associated throwable
    val throwable = errorLoadState?.error

}

CodePudding user response:

I handled server error in lazy row here, line 206 Github

  • Related