4

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.

Roy Hayek
  • 43
  • 2
  • 4
  • Please don't post pictures of code or other text. Copy the original text to your question and use the code format tool. – James Z Sep 20 '21 at 04:27
  • 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. – Community Sep 23 '21 at 08:50

2 Answers2

7

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

dlam
  • 3,547
  • 17
  • 20
  • 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(https://github.com/android/nowinandroid/blob/fe4e8ec7c1b4438cda8d3cd7962ebc7b407f2263/feature-interests/src/main/java/com/google/samples/apps/nowinandroid/feature/interests/InterestsViewModel.kt#L58) we only have access to the `Data`. – Dr.jacky Jul 25 '22 at 18:52
  • 1
    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. – dlam Aug 01 '22 at 17:24
  • 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: https://github.com/KsArt-IT/TMDB_Movies/blob/25e58c1ca6f03ca26a518b42d753fe95b7b9b37b/app/src/main/java/ru/ksart/tmdb_movies/ui/top_rated/MoviesTopRatedViewModel.kt#L59 – Dr.jacky Aug 02 '22 at 18:05
  • 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? – dlam Aug 03 '22 at 21:33
0

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
        }
    }
}
Yasser AKBBACH
  • 539
  • 5
  • 7