suspend method inside runInTransaction block - android

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

Related

How to understand and improve async-await in coroutines?

I would like to improve my async-await in coroutines.
This is my solution
val coroutineContext: CoroutineContext
get() = job + Dispatchers.IO
[...]
lifecycleScope.launch(coroutineContext) {
async {
client = viewModel.getClientItem(clientId)
}.await()
if (inEditMode != null) {
changedEditMode = true
}
[...]
#Marko Topolnik wrote in Why does this coroutine block UI Thread?
Note: this post dates back to the pre-release version of coroutines. I updated the names of dispatchers to match the release version.
runBlocking is not the way to start a coroutine on the UI thread because, as its name says, it will block the hosting thread until the coroutine is done. You have to launch it in the Main context and then switch to the Default context for the heavyweight operation. You should also drop the async-await pair and use withContext:
button1.setOnClickListener {
launch(Main) {
withContext(Default) {
Thread.sleep(5000L)
}
textView1.text = "Done! UI was not blocked :-)"
}
}
withContext will suspend the coroutine until done and then resume it in the parent context, which is Main.
Why doing it with Main Dispatchers and then withContext using Default dispatchers is betters solution than async. It will also block main thread and work the same what is the difference. And how to handle this approaches?
Greetings
EDIT:
My other solution is this below or withContext(Dispatchers.IO)
lifecycleScope.launch(Dispatchers.Main) {
withContext(Dispatchers.Default) {
client = viewModel.getItem(clientId)
}
[...]
}
Now as I read I do not use runBlocking and don't block main thread?
In that configuration it works the same as with async-await.
In your opinin that's better solution
?
Why doing it with Main Dispatchers and then withContext using Default dispatchers is betters solution than async. It will also block main thread and work the same what is the difference
withContext doesn't block, it suspends. The terminology might should similar but the behaviour is very different. While Thread.sleep is executing on the Default dispatcher (and thus on one of this dispatcher's threads), the main thread can keep running other code. The coroutine launched via launch(Main) here is in a suspended state, which means the main dispatcher knows it can execute other things instead. The linked answer here explains why runBlocking was a bad idea, because no matter what you launch in it runBlocking has to block the current thread while it's executing coroutines inside.
Now back to your code. Note that async { doStuff() }.await() makes little sense. It is equivalent to simply doStuff().
Another thing is that you're passing a job explicitly to launch via the context here. This doesn't seem right, and could lead to problems. If you want to use Dispatchers.IO, you don't need to pass a job too.
That said, even Dispatchers.IO seems like a stretch here. You should instead make viewModel.getClientItem a suspend function (if not already), and leave the responsibility of using the right dispatcher to this function. Maybe you don't even need IO at all here, or maybe it's an HTTP or DB call that already has its own thread pool anyway.
So I would replace your whole code with:
lifecycleScope.launch {
client = viewModel.getClientItem(clientId)
if (inEditMode != null) {
hangedEditMode = true
}
}

Using coroutines to access the database

So far I've been using this pattern whenever I want to access the database:
runBlocking {
launch {
// fetch something from the database and put it to some view
}
}
Now that I'm diving deeper into Kotlin coroutines, I'm increasingly convinced, that this is a bad pattern. Essentially, I might as well just allowMainThreadQueries, as my pattern blocks the main thread anyway.
Unfortunately, I haven't found a proper pattern yet. How to effectively use Kotlins coroutines to access the database?
Is runBlocking the only entry point into coroutines?
Consider this scenario:
override fun onCreate() {
setContentView(someLayout)
// concurrently fetch something from the database and put it in some view
// onCreate may return before this has finished
someButton.setOnClickListener {
// concurrently insert or update something in the database
}
}
You should never use runBlocking in an Android project, unless you are mixing Kotlin coroutines code with some Java code that can't use coroutines and it needs a way to call some coroutine in a blocking manner on one of its own background threads. In this case, you might use runBlocking to create a bridge function for the Java code to call, but you would never call this function from Kotlin and certainly never call it from the main thread. Calling blocking code on the main thread freezes the UI, which makes your app feel janky and risks triggering an ANR (application not responding) error.
The correct way to begin a coroutine is to use a CoroutineScope to launch your coroutine. These are already provided for you by the Android Jetpack framework for Activities, Fragments, and ViewModels.
In an Activity, use lifecycleScope.launch. In a Fragment, you should usually use viewLifecycleOwner.lifecycleScope.launch. In a ViewModel, use viewModelScope.launch.
What does using a CoroutineScope instead of runBlocking do? It prevents the long-running suspending actions (like reading the database from the disk) from blocking the main thread and freezing your UI. And it automatically cancels the long-running work when the Activity/Fragment/ViewModel is torn down, so it prevents memory leaks and wasted resources.
Assume that you are using Room,
runBlocking and allowMainThreadQueries are usually used for Test purpose and you should never use them in release product.
what allowMainThreadQueries do is give you permission to access database from Main Thread which is you should Never do, because it may freeze the UI.
use lifecycleScope.launch to launch coroutine from Fragment/Activity or viewModelScope.launch from ViewModel, you might need to explicitly add the dependencies
def lifecycleVersion = '2.4.0'
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
// Lifecycles only (without ViewModel or LiveData)
implementation("androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion")
Lifecycle release note https://developer.android.com/jetpack/androidx/releases/lifecycle
you should call the database operation from the ViewModel to prevent cancelation from configuration change, If the user rotate the screen while the operation is in progress, it'll be canceled and the result won't be cached.
In Activity/Fragment
// read data from db
lifecycleScope.launch {
viewModel.someData.collect {
//do some stuff
}
}
// insert
someButton.setOnClickListener {
viewModel.insertToDatabase(someData)
}
In ViewModel
class MainViewModel (val dao: Dao) : ViewModel() {
// you should expose your data as Observable
val someData : Flow<List<SomeData>> = dao.getAllSomeData()
fun insertToDatabase(data:SomeData) = viewModelScope.launch {
dao.insert(data)
}
}
// query
#Query("SELECT * FROM some_data_table")
abstract fun getAllSomeData(): Flow<List<SomeData>>

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?

Meaning of this code "suspend fun get(url: String) = withContext(Dispatchers.IO){/*...*/}"

I am new to android development.When I was reading medium post https://medium.com/androiddevelopers/coroutines-on-android-part-i-getting-the-background-3e0e54d20bb I came across this code:
suspend fun get(url: String) = withContext(Dispatchers.IO){/*...*/}
which I could not understand. I have tried Search but I could not find code with similar syntax.Can somebody please explain it?
It's releated to asynchronous or non-blocking programming using Coroutines. It's a suspending function which can suspend the execution of a coroutine.
The withContext lets your function return a value ( you can also use launch which will return a job ).
From docs:
Calls the specified suspending block with a given coroutine context, suspends until it completes, and returns the result. Read more here.
The Dispatchers.IO is the default instance of coroutine dispatcher for background coroutine. Read more here.

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