Search code examples
androidkotlinandroid-architecture-componentsandroid-paging

How to add Item in PagedList of Paging Library


I am trying to add comment functionality in my App. Just like Instagram or Facebook where comment will be added on comment list after being successfully saved to the database. I am using Android Architecture Components (ViewModel, LiveData and Paging Library).

Here is my ViewModel where I think I should update my PagedList

class CommentsViewModel : ViewModel() {
lateinit var networkState: LiveData<NetworkState>
private val executor: Executor
private lateinit var tDataSource: LiveData<CommentsDataSource>
private lateinit var dataFactory: CommentsDataFactory
private var repository: CommentsRepository
private var commentsList: LiveData<PagedList<Comment>>? = null

init {
    executor = Executors.newFixedThreadPool(5)
    repository = CommentsRepository()
}

fun getComments(postId: Int): LiveData<PagedList<Comment>>? {
    dataFactory = CommentsDataFactory(executor, postId)

    tDataSource = dataFactory.mutableLiveData

    networkState = Transformations.switchMap(dataFactory.mutableLiveData) {
        it.networkState
    }

    val pagedListConfig = PagedList.Config.Builder()
            .setEnablePlaceholders(true)
            .setInitialLoadSizeHint(Constants.INITIAL_LOAD)
            .setPageSize(Constants.PAGE_SIZE)
            .build()

    commentsList = LivePagedListBuilder(dataFactory, pagedListConfig)
            .setFetchExecutor(executor)
            .build()
    return commentsList
}

//This method here brings errors.
fun addComment(comment: Comment) {
    commentsList?.value?.add(comment)
}

fun postComment(map: Map<String, String>): LiveData<NetworkState> {
    repository.postComment(map)
    return repository.mNetworkState
}}

How to do it Correctly?.

CommentsDataFactory

class CommentsDataFactory(private val executor: Executor, private val postId: Int)
: DataSource.Factory<Int, Comment>() {

val mutableLiveData = MutableLiveData<CommentsDataSource>()

override fun create(): DataSource<Int, Comment>? {
    val dataSource = CommentsDataSource(executor, postId)
    mutableLiveData.postValue(dataSource)
    return dataSource
}}

CommentsDataSource

class CommentsDataSource(private val retryExecutor: Executor, private val post_id: Int)
: PageKeyedDataSource<Int, Comment>() {

private var retry: (() -> Any)? = null
private val apiClient = getClient()

val networkState = MutableLiveData<NetworkState>()

val initialLoad = MutableLiveData<NetworkState>()

fun retryAllFailed() {
    val prevRetry = retry
    retry = null
    prevRetry?.let {
        retryExecutor.execute {
            it.invoke()
        }
    }
}

override fun loadBefore(
        params: LoadParams<Int>,
        callback: LoadCallback<Int, Comment>) {
}

override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, Comment>) {
    networkState.postValue(NetworkState.LOADING)
    apiClient.getComments(post_id, params.key).enqueue(
            object : retrofit2.Callback<List<Comment>> {
                override fun onFailure(call: Call<List<Comment>>, t: Throwable) {
                    retry = {
                        loadAfter(params, callback)
                    }
                    networkState.postValue(NetworkState.error(t.message ?: "unknown err"))
                }

                override fun onResponse(
                        call: Call<List<Comment>>,
                        response: Response<List<Comment>>) {
                    when {
                        response.isSuccessful -> {
                            retry = null
                            callback.onResult(response.body() as MutableList<Comment>, params.key + 10)
                            networkState.postValue(NetworkState.LOADED)
                        }
                        response.code() == 404 -> networkState.postValue(NetworkState.END)
                        else -> {
                            retry = {
                                loadAfter(params, callback)
                            }
                            networkState.postValue(
                                    NetworkState.error("error code: ${response.code()}"))
                        }
                    }
                }
            }
    )
}

override fun loadInitial(
        params: LoadInitialParams<Int>,
        callback: LoadInitialCallback<Int, Comment>) {

    val request = apiClient.getComments(post_id, 0)

    networkState.postValue(NetworkState.LOADING)
    initialLoad.postValue(NetworkState.LOADING)

    try {
        retry = null
        val response = request.execute()

        when {
            response.isSuccessful -> {
                networkState.postValue(NetworkState.LOADED)
                initialLoad.postValue(NetworkState.LOADED)
                callback.onResult(response.body() as MutableList<Comment>, 0, 10)
            }
            response.code() == 404 -> networkState.postValue(NetworkState.END)
            else -> networkState.postValue(
                    NetworkState.error("error code: ${response.code()}"))
        }

    } catch (ioException: IOException) {
        retry = {
            loadInitial(params, callback)
        }
        val error = NetworkState.error(ioException.message ?: "unknown error")
        networkState.postValue(error)
        initialLoad.postValue(error)
    }
}}

Solution

  • The correct way to do that, is:

    1. Send your newly added comment to your remote server (based on your code, you are fetching all comments from server). For that you will invoke some ViewModel method, like addComment(). Which will invoke some method from your apiClient.

    2. Call invalidate on your DataSource (also in ViewModel, even in the same addComment() method), it will trigger your PagedList update.

      commentsDataSource.invalidate();