Search code examples
androidkotlinandroid-architecture-componentsandroid-livedataandroid-viewmodel

How to observe PagedList data?


I'm using Paging Library and Android Architecture Components. I simply want to observe pagedlist livedata and update my RecyclerView when there is a change.

I'm observing isLoadingLiveData, isEmptyLiveData and errorLiveData objects which are MediatorLiveData objects created in my ViewModel and observed in my fragment. And also observing resultLiveData which returns the fetched Gist list from remote.

In my ViewModel, I created a PagedList LiveData and whenever it's data changed, I wanted to update isLoadingLiveData, isEmptyLiveData, errorLiveData and PagedListAdapter. Therefore, I defined isLoadingLiveData, isEmptyLiveData, errorLiveData and resultLiveData as MediatorLiveData objects. I added resultLiveData as a source of these objects. So when resultLiveData has changed, these objects' onChanged methods will be called. And resultLiveData is depend on userNameLiveData, so when userNameLiveData has changed, allGistsLiveData will be called and it will fetch the data. For example when the user swipe the list, I'm setting userNameLiveData and doing network call again.

My ViewModel:

private val userNameLiveData = MutableLiveData<String>()
private var gists: LiveData<PagedList<Gist>>? = null

val allGistsLiveData: LiveData<PagedList<Gist>>
    get() {
        if (null == gists) {
            gists = GistModel(NetManager(getApplication()), getApplication()).getYourGists(userNameLiveData.value!!).create(0,
                    PagedList.Config.Builder()
                            .setPageSize(PAGED_LIST_PAGE_SIZE)
                            .setInitialLoadSizeHint(PAGED_LIST_PAGE_SIZE)
                            .setEnablePlaceholders(PAGED_LIST_ENABLE_PLACEHOLDERS)
                            .build())
        }
        return gists!!
    }

val resultLiveData = MediatorLiveData<LiveData<PagedList<Gist>>>().apply {
    this.addSource(userNameLiveData) {
        gists = null
        this.value = allGistsLiveData
    }
}


val isLoadingLiveData = MediatorLiveData<Boolean>().apply {
    this.addSource(resultLiveData) { this.value = false }
}

val isEmptyLiveData = MediatorLiveData<Boolean>().apply {
    this.addSource(resultLiveData) { this.value = false }
}

val errorLiveData = MediatorLiveData<Boolean>().apply {
    this.addSource(resultLiveData) {
        if (it == null) {
            this.value = true
        }
    }
}

fun setUserName(userName: String) {
    userNameLiveData.value = userName
}

and my fragment:

override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    viewModel.isEmptyLiveData.observe(this@YourGistsFragment, Observer<Boolean> { isVisible ->
        emptyView.visibility = if (isVisible!!) View.VISIBLE else View.GONE
    })

    viewModel.isLoadingLiveData.observe(this@YourGistsFragment, Observer<Boolean> {
        it?.let {
            swipeRefreshLayout.isRefreshing = it
        }
    })

    viewModel.errorLiveData.observe(this@YourGistsFragment, Observer<Boolean> {
        it?.let {
            showSnackBar(context.getString(R.string.unknown_error))
        }
    })

    viewModel.setUserName("melomg")
    viewModel.resultLiveData.observe(this@YourGistsFragment, Observer { it -> gistAdapter.setList(it?.value) })
}


override fun onRefresh() {
    viewModel.setUserName("melomg")
}

my repository:

fun getYourGists(userName: String): LivePagedListProvider<Int, Gist> {
    return remoteDataSource.getYourGists(userName, GitHubApi.getGitHubService(context))
}

my remoteDataSource:

fun getYourGists(username: String, dataProvider: GitHubService): LivePagedListProvider<Int, Gist> {
    return object : LivePagedListProvider<Int, Gist>() {
        override fun createDataSource(): GistTiledRemoteDataSource<Gist> = object : GistTiledRemoteDataSource<Gist>(username, dataProvider) {
            override fun convertToItems(items: ArrayList<Gist>?): ArrayList<Gist>? {
                return items
            }
        }
    }
}

I tried to create this solution from this project. But my problem is resultLiveData has been changing without waiting the network call response and therefore the result of response ignored and my ui is being updated before the data has arrived. Since resultLiveData is changing before request and therefore there is no data yet. Simply how can I observe pagedlist livedata?


Solution

  • Finally, I found a working solution but I don't think it is a best solution. So if you have any ideas please don't hesitate to answer. Here is my solution:

    I gave up from wrapping my PagedList data with MediatorLiveData. I still leave my all other live data(isLoadingLiveData, isEmptyLiveData, errorLiveData) as MediatorLiveData but added their source when my gists PagedList LiveData initialized. Here is the code:

    private var gists: LiveData<PagedList<Gist>>? = null
    private val userNameLiveData = MutableLiveData<String>()
    val isLoadingLiveData = MediatorLiveData<Boolean>()
    val isEmptyLiveData = MediatorLiveData<Boolean>()
    val errorLiveData = MediatorLiveData<ErrorMessage>()
    
    val allGistsLiveData: LiveData<PagedList<Gist>>
        get() {
            if (gists == null) {
                    gists = GistModel(NetManager(getApplication()), getApplication()).getYourGists(userNameLiveData.value!!).create(0,
                            PagedList.Config.Builder()
                                    .setPageSize(Constants.PAGED_LIST_PAGE_SIZE)
                                    .setInitialLoadSizeHint(Constants.PAGED_LIST_PAGE_SIZE)
                                    .setEnablePlaceholders(Constants.PAGED_LIST_ENABLE_PLACEHOLDERS)
                                    .build())
    
    
                    isLoadingLiveData.addSource(gists) { isLoadingLiveData.value = false }
                    isEmptyLiveData.addSource(gists) {
                        if (it?.size == 0) {
                            isEmptyLiveData.value = true
                        }
                    }
                    errorLiveData.addSource(gists) {
                        if (it == null) {
                            errorLiveData.value = ErrorMessage(errorCode = ErrorCode.GENERAL_ERROR)
                        }
                    }
                }
            }
            return gists!!
        }
    
    
    fun setUserName(userName: String) {
        isLoadingLiveData.value = true
        userNameLiveData.value = userName
        gists = null
        allGistsLiveData
    }