zedlabs
zedlabs

Reputation: 523

how to correctly implement kotlin coroutines with Paging Library?

I have an android app in which i want to implement Kotlin Couroutines, the confusion I am having is where do I use the coroutine. I have the viewModel as-

class PostViewModel : ViewModel() {

    var postPagedList: LiveData<PagedList<UnsplashImageDetails>>? = null
    private var postLiveDataSource: LiveData<PageKeyedDataSource<Int, UnsplashImageDetails>>? = null

    var popularPagedList: LiveData<PagedList<UnsplashImageDetails>>? = null
    private var popularLiveDataSource: LiveData<PageKeyedDataSource<Int, UnsplashImageDetails>>? = null

    init {
        val postDataSourceFactory = PostDataSourceFactory()
        val popularDataSourceFactory = PopularDataSourceFactory()

        postLiveDataSource = postDataSourceFactory.getPostLiveDataSource()
        popularLiveDataSource = popularDataSourceFactory.getPopularLiveDataSource()

        val config: PagedList.Config = (PagedList.Config.Builder()).setEnablePlaceholders(false)
            .setPageSize(PostDataSource().PAGE_SIZE).build()
        val configPop: PagedList.Config = (PagedList.Config.Builder()).setEnablePlaceholders(false)
            .setPageSize(PopularDataSource().PAGE_SIZE).build()

        postPagedList = LivePagedListBuilder(postDataSourceFactory, config).build()
        popularPagedList = LivePagedListBuilder(popularDataSourceFactory, configPop).build()
    }
}

where should I use the async method, within this activity or in the repository class where I fetch data through retrofit.

Upvotes: 3

Views: 4737

Answers (1)

apksherlock
apksherlock

Reputation: 8371

In this case, coroutines should fire in the PostDataSource.If you are wondering whether you should use GlobalScope there, the answer is not. There is a more elegant way of doing this. You should find a way for canceling jobs to prevent memory leaks also. That's why I wrote this article here, which solves this particular problem.

You should find a way to manage coroutines from your ViewModel , but you should fire them in the DataSource .

The best way to do it.

Create a data class:

data class Listing<T>(
    val pagedList: LiveData<PagedList<T>>,
    val networkState: LiveData<NetworkState>, //initial state
    val refreshState: LiveData<NetworkState>, // second state, after first data loaded
    val refresh: () -> Unit, // signal the data source to stop loading, and notify its callback
    val retry: () -> Unit,  // remake the call
    val clearCoroutineJobs: () -> Unit // the way to stop jobs from running since no lifecycle provided )

enum class Status {
    RUNNING,
    SUCCESS,
    FAILED
}

@Suppress("DataClassPrivateConstructor")
data class NetworkState private constructor(
    val status: Status,
    val msg: String? = null
) {
    companion object {
        val LOADED =
            NetworkState(Status.SUCCESS)
        val LOADING =
            NetworkState(Status.RUNNING)

        fun error(msg: String?) = NetworkState(
            Status.FAILED,
            msg
        )
    }
}

And then, your data source factory should be something like this.

What you benefit from this approach is that you can cancel jobs in the onCleared method in the ViewModel:

override fun onCleared() {
        super.onCleared()
        //finish the coroutines opened jobs
        listing.clearCoroutineJobs.invoke()
    }

Note that this is also a guide provided by Igit, in this github repo.

I do agree that the solution should have been simpler, but if you think about it, coroutines and concurrency overall has nothing to do with simplicity, you are dealing with threads here.

Upvotes: 7

Related Questions