Kotlin in Android - Suspend Functions in Room - android

I was looking at the sample at https://github.com/android/architecture-samples/tree/dev-dagger/app/src/main/java/com/example/android/architecture/blueprints/todoapp/data/source/local of the dev-dagger branch and in the TasksLocalDataSource.kt file they have the following method:
override suspend fun getTasks(): Result<List<Task>> = withContext(ioDispatcher) {
return#withContext try {
Success(tasksDao.getTasks())
} catch (e: Exception) {
Error(e)
}
}
By using withContext with an IO as dispatcher, they want the coroutine to run on an IO thread. But the Room request tasksDao.getTasks() inside the method is a suspend function. And in the codelab at https://codelabs.developers.google.com/codelabs/kotlin-coroutines/#8, they say that Room takes care of running the request (here: getTasks() ) on a background thread when it is a suspend function which is the case here.
So, isn't too much to use also a withContext(ioDispatcher) ? Could I not rewrite the method above also like the following way ? :
override suspend fun getTasks(): Result<List<Task>> {
return Success(tasksDao.getTasks())
}

Yes, this is exactly what you should write. There still appears to be a lot of legacy in the docs, from the time before they introduced suspendable Room calls. It is a waste both in terms of readability and performance to force a suspendable function call into the IO dispatcher.

Related

Is there a way to know if your coroutine has been canceled from within a suspend function

I'm using a CoroutineWorker but all my business logic is a separate class which I start using a suspend function. I would prefer to keep all the logic in this class but I need to know if the work request has been canceled. Is there someway to know in the suspend function if it's been canceled?
To determine whether the current Job is still active (has not completed and was not cancelled yet) in the suspend function you can use isActive property. Example:
suspend fun callApi() = withContext(Dispatchers.IO) {
//...
if (!isActive) {
// coroutine is not active
}
}
Another common way to check for cancellation is to call ensureActive(), which is an extension function available for Job, CoroutineScope, and CoroutineContext. Example:
suspend fun callApi() = withContext(Dispatchers.IO) {
//...
ensureActive() // it throws a CancellationException and will stop the execution
}
Repeating gmk57's answer:
"Plain suspend function (without withContext) doesn't provide isActive flag, but you can use coroutineContext.isActive"
With a suspend function you do have access to your corountineContext. Accessing that will give you access to all of the same properties that you can access from directly inside the context.

Coroutine keeps crashing without showing error

I'm developing in MVP. In my Presenter, I call my Repository thanks to the suspend function. In this suspend function, I launch a Coroutine - not on the main thread.
When this coroutine is finished, I want to execute some code: I am using withContext() in order to do so.
In my Repository, I am launching a Coroutine (and maybe I am wrong) to insert my data, using DAO, in my Room database.
When I debug my application, it goes into my Presenter but crashes before going into my Repository.
Presenter
override suspend fun insertUserResponse(activity: Activity, data: UserResponse) {
scope.launch(Dispatchers.IO) {
try {
userResponseRepository.insertUserResponse(data)
withContext(Dispatchers.Main) {
redirectToClientMainPage(activity)
}
} catch (error: Exception) {
parentJob.cancel()
Log.e("ERROR PRESENTER", "${error.message}")
}
}
}
Repository
override suspend fun insertUserResponse(userResponse: UserResponse) {
GlobalScope.launch(Dispatchers.IO) {
try {
val existingUser: UserResponse? =
userResponseDAO.searchUserByID(userResponse.profilePOJO.uniqueID)
existingUser?.let {
userResponseDAO.updateUser(userResponse)
} ?: userResponseDAO.insertUser(userResponse)
} catch (error: Exception) {
Log.e("ERROR REPOSITORY", "${error.message}")
}
}
}
I have no error shown in my logcat.
EDIT:
Scope initialization
private var parentJob: Job = Job()
override val coroutineContext: CoroutineContext
get() = uiContext + parentJob
private val scope = CoroutineScope(coroutineContext)
val uiContext: CoroutineContext = Dispatchers.Main (initialized in my class constructor)
Stack trace
I finally found the answer!
Thanks to #sergiy I read part II of this article https://medium.com/androiddevelopers/coroutines-on-android-part-ii-getting-started-3bff117176dd and it mentions that you can't catch error except Throwable and CancellationException.
So instead of catching Exception, I traded it for Throwable. And finally I had an error shown in my logcat.
I am using Koin to inject my repository and all. I was missing my androidContext() in my Koin application.
That's it.
Without stacktrace it's hard to help you.
Here is an article, that might be helpful. Replacing ViewModel mentioned in the article with Presenter in your case you can get several general recommendations in using coroutines:
As a general pattern, start coroutines in the ViewModel (Read: Presenter)
I don't see the need to launch one more coroutine in your case in Repository.
As for switching coroutine's context for Room:
Room uses its own dispatcher to run queries on a background thread. Your code should not use withContext(Dispatchers.IO) to call suspending room queries. It will complicate the code and make your queries run slower.
I couldn't find the same recommendation in official docs, but it's mentioned in one of the Google code labs.
Both Room and Retrofit make suspending functions main-safe.
It's safe to call these suspend funs from Dispatchers.Main, even though they fetch from the network and write to the database.
So you can omit launch (as well as withContext) in Repository, since Room guarantees that all methods with suspend are main-safe. That means that they can be called even from the main thread. Also you can not to define Dispatchers.IO explicitly in Presenter.
One more thing. If Presenter's method insertUserResponse is suspend, then you call it from another launched coroutine, don't you? In that case, why you launch one more coroutine inside this method? Or maybe this method shouldn't be suspend?

suspend method inside runInTransaction block

I have a compilation error using the code below:
Suspension functions can be called only within coroutine body
Can someone explain to me why? What do I need to do to make it work (without using the #Transaction annotation)?
override suspend fun replaceAccounts(newAccounts: List<Account>) {
database.runInTransaction {
database.accountDao().deleteAllAccounts() // I have the error on this line
database.accountDao().insertAccounts(newAccounts) // Here too
}
}
#Dao
abstract class AccountDao : BaseDao<AccountEntity> {
#Query("DELETE FROM Account")
abstract suspend fun deleteAllAccounts()
}
Thanks in advance for your help
For suspend functions you should use withTransaction instead of runInTransaction
IO-bound and other long-running operations (such as database or API calls) are restricted from running in the main thread directly (which could otherwise cause your program to become unresponsive). Coroutines are like light-weight threads which run, asynchronously, within a thread.
I suggest reading through the Coroutines guide at https://kotlinlang.org/docs/reference/coroutines/coroutine-context-and-dispatchers.html
To answer your question, you need to setup a coroutine scope, and dispatcher thread for your coroutine to run on. The simplest is something like:
GlobalScope.launch(Dispatchers.IO) {
replaceAccounts(newAccounts)
}
which would run your coroutine in the GlobalScope (the coroutine's "lifecycle" is bound to the lifecycle of the entire application), on the IO thread (a thread outside of the main thread which handles IO tasks).
EDIT
I do like #IR42's answer. To build upon that, the usage of withTransaction in this case allows Room to handle the thread in which the database operations are performed on, and helps to limit concurrency to the database.
GlobalScope.launch(Dispatchers.Main) {
replaceAccounts(newAccounts)
}
override suspend fun replaceAccounts(newAccounts: List<Account>) {
database.withTransaction {
database.accountDao().deleteAllAccounts() // I have the error on this line
database.accountDao().insertAccounts(newAccounts) // Here too
}
}
See more information on this article by one of Room's own: https://medium.com/androiddevelopers/threading-models-in-coroutines-and-android-sqlite-api-6cab11f7eb90

binding to a suspended function in android

I want to bind a button click to a suspended function inside viewmodel.
this is my code:
RegistrationActivityViewModel.kt
suspend fun register() {
if (validateFields()) {
val result = SourceplusplusApiService.invoke().registerUser(username.value!!, email.value!!, password.value!!).await()
isRegistrationCompleted.value = getResultValue(result)
}
}
activity_registration.xml
<Button
android:text="Register"
android:onClick="#{()->viewmodel.register()}"
android:textSize="16sp" />
i get a databinding error that says ActivityRegistrationBindingImpl not generated. after searching a lot i realized that when i remove the suspend keyword and comment the code inside, it works fine but it has to be a suspended function.
Does anyone know how to fix it?
You cannot data bind to a suspend function, and IMHO a viewmodel should not be exposing a suspend function in the first place. I recommend:
Step #1: Remove the suspend keyword from register()
Step #2: Rewrite register() to run your code in a suitable coroutine scope, so any suspend functions that it calls are handled properly:
fun register() {
viewModelScope.launch(Dispatchers.Main) {
if (validateFields()) {
val result = SourceplusplusApiService.invoke().registerUser(username.value!!, email.value!!, password.value!!).await()
isRegistrationCompleted.value = getResultValue(result)
}
}
}
Here, I am using the viewModelScope option provided by androidx.lifecycle:lifecycle-viewmodel-ktx version 2.1.0-alpha01 and newer. Alternatively, manage your own coroutine scope. Dispatchers.Main will ensure that any results of that work is available to you on the Android main application thread.
Now, your data binding expression can refer to register(), while you still have a coroutine scope for calling downstream suspend functions.

Kotlin coroutines `runBlocking`

I am learning Kotlin coroutines. I've read that runBlocking is the way to bridge synchronous and asynchronous code. But what is the performance gain if the runBlocking stops the UI thread?
For example, I need to query a database in Android:
val result: Int
get() = runBlocking { queryDatabase().await() }
private fun queryDatabase(): Deferred<Int> {
return async {
var cursor: Cursor? = null
var queryResult: Int = 0
val sqlQuery = "SELECT COUNT(ID) FROM TABLE..."
try {
cursor = getHelper().readableDatabase.query(sqlQuery)
cursor?.moveToFirst()
queryResult = cursor?.getInt(0) ?: 0
} catch (e: Exception) {
Log.e(TAG, e.localizedMessage)
} finally {
cursor?.close()
}
return#async queryResult
}
}
Querying the database would stop the main thread, so it seems that it would take the same amount of time as synchronous code? Please correct me if I am missing something.
runBlocking is the way to bridge synchronous and asynchronous code
I keep bumping into this phrase and it's very misleading.
runBlocking is almost never a tool you use in production. It undoes the asynchronous, non-blocking nature of coroutines. You can use it if you happen to already have some coroutine-based code that you want to use in a context where coroutines provide no value: in blocking calls. One typical use is JUnit testing, where the test method must just sit and wait for the coroutine to complete.
You can also use it while playing around with coroutines, inside your main method.
The misuse of runBlocking has become so widespread that the Kotlin team actually tried to add a fail-fast check which would immediately crash your code if you call it on the UI thread. By the time they did this, it was already breaking so much code that they had to remove it.
Actually you use runBlocking to call suspending functions in "blocking" code that otherwise wouldn't be callable there or in other words: you use it to call suspend functions outside of the coroutine context (in your example the block passed to async is the suspend function). Also (more obvious, as the name itself implies already), the call then is a blocking call. So in your example it is executed as if there wasn't something like async in place. It waits (blocks interruptibly) until everything within the runBlocking-block is finished.
For example assume a function in your library as follows:
suspend fun demo() : Any = TODO()
This method would not be callable from, e.g. main. For such a case you use runBlocking then, e.g.:
fun main(args: Array<String>) {
// demo() // this alone wouldn't compile... Error:() Kotlin: Suspend function 'demo' should be called only from a coroutine or another suspend function
// whereas the following works as intended:
runBlocking {
demo()
} // it also waits until demo()-call is finished which wouldn't happen if you use launch
}
Regarding performance gain: actually your application may rather be more responsive instead of being more performant (sometimes also more performant, e.g. if you have multiple parallel actions instead of several sequential ones). In your example however you already block when you assign the variable, so I would say that your app doesn't get more responsive yet. You may rather want to call your query asynchronously and then update the UI as soon as the response is available. So you basically just omit runBlocking and rather use something like launch. You may also be interested in Guide to UI programming with coroutines.

Categories

Resources