3

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)
    }
}}
Nux
  • 5,890
  • 13
  • 48
  • 74

1 Answers1

3

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();
    
Denysole
  • 3,903
  • 1
  • 20
  • 28
  • @Nux based on your `DataSource` code, you are loading comments from server. So you need send new comment to server and then fetch it to show. I've updated my answer. – Denysole Oct 30 '18 at 18:38
  • 1
    So that's means I will have to fetch list of comments again from server to get one added? – Nux Oct 30 '18 at 18:49
  • Yeah, because someone else also might add comment meanwhile and that's mean that your data is invalid and you should `invalidate()` it. – Denysole Oct 30 '18 at 19:12
  • @AlexeyDenysenko Consider the same problem. But here my thing is I am showing a chat history. What I can do to add new messages? Invalidating and getting all back the messages will use more bandwidth. – Sarath Kumar Rajendran Jan 31 '20 at 07:16