How to handle Kotlin Jetpack Paging 3 exceptions?
Asked Answered
C

3

8

I am new to kotlin and jetpack, I am requested to handle errors (exceptions) coming from the PagingData, I am not allowed to use Flow, I am only allowed to use LiveData.

This is the Repository:

class GitRepoRepository(private val service: GitRepoApi) {

    fun getListData(): LiveData<PagingData<GitRepo>> {
        return Pager(
            // Configuring how data is loaded by adding additional properties to PagingConfig
            config = PagingConfig(
                pageSize = 20,
                enablePlaceholders = false
            ),
            pagingSourceFactory = {
                // Here we are calling the load function of the paging source which is returning a LoadResult
                GitRepoPagingSource(service)
            }
        ).liveData
    }
}

This is the ViewModel:

class GitRepoViewModel(private val repository: GitRepoRepository) : ViewModel() {

    private val _gitReposList = MutableLiveData<PagingData<GitRepo>>()

    suspend fun getAllGitRepos(): LiveData<PagingData<GitRepo>> {
        val response = repository.getListData().cachedIn(viewModelScope)
        _gitReposList.value = response.value
        return response
    }

}

In the Activity I am doing:

  lifecycleScope.launch {
            gitRepoViewModel.getAllGitRepos().observe(this@PagingActivity, {
                recyclerViewAdapter.submitData(lifecycle, it)
            })
        }

And this is the Resource class which I created to handle exceptions (please provide me a better one if there is)

data class Resource<out T>(val status: Status, val data: T?, val message: String?) {

    companion object {
        fun <T> success(data: T?): Resource<T> {
            return Resource(Status.SUCCESS, data, null)
        }

        fun <T> error(msg: String, data: T?): Resource<T> {
            return Resource(Status.ERROR, data, msg)
        }

        fun <T> loading(data: T?): Resource<T> {
            return Resource(Status.LOADING, data, null)
        }
    }
}

As you can see I am using Coroutines and LiveData. I want to be able to return the exception when it occurs from the Repository or the ViewModel to the Activity in order to display the exception or a message based on the exception in a TextView.

Clank answered 19/9, 2021 at 20:18 Comment(2)
Please don't post pictures of code or other text. Copy the original text to your question and use the code format tool.Debidebilitate
Please clarify your specific problem or provide additional details to highlight exactly what you need. As it's currently written, it's hard to tell exactly what you're asking.Lesser
I
12

Your GitRepoPagingSource should catch retryable errors and pass them forward to Paging as a LoadResult.Error(exception).

class GitRepoPagingSource(..): PagingSource<..>() {
    ...
    override suspend fun load(..): ... {
        try {
            ... // Logic to load data
        } catch (retryableError: IOException) {
            return LoadResult.Error(retryableError)
        }
    }
}

This gets exposed to the presenter-side of Paging as LoadState, which can be reacted to via LoadStateAdapter, .addLoadStateListener, etc as well as .retry. All of the presenter APIs from Paging expose these methods, such as PagingDataAdapter: https://developer.android.com/reference/kotlin/androidx/paging/PagingDataAdapter

Ichang answered 21/9, 2021 at 20:30 Comment(4)
If we use addLoadStateListener and want to follow UDF (having a sealed class with Data, Loading, Error), how we could use that data class Error? Cause here(github.com/android/nowinandroid/blob/…) we only have access to the Data.Keri
The thing is that PagingData is not stateful, it only does work while there is a UI observing it and the UI is where this state is collected. So load errors from Paging are currently only visible from the UI-layer, and if you want single object UDF, you need to send that as a UI signal back up to VM separately.Ichang
So you mean when the error is already received on the UI, instead of showing the relevant message on the UI, call a viewModel.passError(...)? so, the ViewModel can update the flow and the UI that observes it in the first place will be called? ------Also, what do you think about this solution: github.com/KsArt-IT/TMDB_Movies/blob/…Keri
So unfortunately you must pass the error from UI back to VM because there is currently no way to collect the state from PagingData without the UI component. From your sample, I didn't look too closely, but it's not immediately clear what you're trying to do. Are you trying to convert Flow of PagingData into a state of success / fail? What constitutes as an error or success? What if you successfully load the initial page, but appending a page fails?Ichang
C
2

You gotta pass your error handler to the PagingSource

class MyPagingSource(
    private val api: MyApi,
    private val onError: (Throwable) -> Unit,
): PagingSource<Int, MyModel>() {

    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, YourModel> {
        try {
            ...
        } catch(e: Exception) {
            onError(e) // <-- pass your error listener here
        }
    }
}
Columella answered 20/2, 2023 at 22:39 Comment(0)
S
1

You can monitor various stages of errors attaching loadState to the pagingAdapter. For eg your first call passed with internet but second paging call failed (appending of further data) because of no internet connection, so you'll get error in loadState.append.

    private fun subscribeLoadStates() {

        gitRepoAdapter.addLoadStateListener { loadState ->
            
            binding.apply {
                when {
                    loadState.refresh is LoadState.Error ->
                        showError(loadState.refresh as? LoadState.Error)

                    loadState.append is LoadState.Error -> 
                        showError(loadState.append as? LoadState.Error)

                    loadState.prepend is LoadState.Error -> 
                        showError(loadState.prepend as? LoadState.Error)
                }
            }
        }
    }
    private fun showError(errorState: LoadState.Error?) {
        errorState?.let {
            Toast.makeText(context, "${it.error.message}", Toast.LENGTH_SHORT)
                .show()
        }
    }
Sheffy answered 28/2 at 5:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.