widjajayd
widjajayd

Reputation: 6253

Insert Data into room database after retrofit success, coroutine problem

I'm using Retrofit to get news record from API server, if it success then it will write data to room database as code below using insertAll, but this code generate an error as follow Cannot access database on the main thread since it may potentially lock the UI for a long period of time

I tried to use Coroutine, withContext(Dispatchers.IO) but I think it's not correct, thank you for any help

suspend fun refreshNews(queryString: String="", page: Int = 1) {
  withContext(Dispatchers.IO) {
    RetrofitClient.instance.getAllNews(buatObjectNewsQuery(queryString), page)
        .enqueue(object : Callback<NewsGetAllResponse> {
            override fun onFailure(call: Call<NewsGetAllResponse>, t: Throwable) {
                Timber.tag(TAG).i("sorry network error")
            }

            override fun onResponse(
                call: Call<NewsGetAllResponse>,
                response: Response<NewsGetAllResponse>
            ) {
                val newslist = response.body()?.asDatabaseModel()
                if (newslist != null) {
                    database.databaseNewsDao.insertAll(*newslist)
                    Timber.tag(TAG).i("dalam refresh jumlah data ${newslist.size}")
                }
            }


        })

  }
}

Upvotes: 1

Views: 1062

Answers (1)

Dominic Fischer
Dominic Fischer

Reputation: 1849

You code should look more like this. Isolating the callback code with suspendCoroutine so that stuff like withContext(Dispatchers.IO) works as you expect.

suspend fun refreshNews(queryString: String="", page: Int = 1) {
    val call = RetrofitClient.instance.getAllNews(buatObjectNewsQuery(queryString), page)
    val response = suspendCoroutine { cont ->
        call.enqueue(object : Callback<NewsGetAllResponse> {
            override fun onFailure(call: Call<NewsGetAllResponse>, t: Throwable) {
                cont.resumeWithException(t)
            }

            override fun onResponse(
                call: Call<NewsGetAllResponse>,
                response: Response<NewsGetAllResponse>
            ) {
                const.resume(response)
            }
        })
    }

    val newslist = response.body()?.asDatabaseModel()
    if (newslist != null) {
        withContext(Dispatchers.IO) {
            database.databaseNewsDao.insertAll(*newslist)
            Timber.tag(TAG).i("dalam refresh jumlah data ${newslist.size}")
        }
    }
}

EDIT:

Ideally you want to make an extension function for that, so it doesn't look so intimidating to read.

suspend fun <T> Call<T>.awaitResponse(): Response<T> {
    return suspendCancellableCoroutine { continuation ->
        continuation.invokeOnCancellation {
            cancel()
        }
        enqueue(object : Callback<T> {
            override fun onResponse(call: Call<T>, response: Response<T>) {
                continuation.resume(response)
            }

            override fun onFailure(call: Call<T>, t: Throwable) {
                continuation.resumeWithException(t)
            }
        })
    }
}

suspend fun refreshNews(queryString: String="", page: Int = 1) {
    val call = RetrofitClient.instance.getAllNews(buatObjectNewsQuery(queryString), page)
    val response = call.awaitResponse()

    val newslist = response.body()?.asDatabaseModel()
    if (newslist != null) {
        withContext(Dispatchers.IO) {
            database.databaseNewsDao.insertAll(*newslist)
            Timber.tag(TAG).i("dalam refresh jumlah data ${newslist.size}")
        }
    }
}

Upvotes: 1

Related Questions