While learning how to use Kotlin Coroutines recently, have been read several relevant articles. But one of them confuse me: Coroutines On Android (part III): Real work
As it pointed out:
Note: 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.
It seems to make sense at the time I saw these paragraph, but when I open an Android project and trying to dive into, the problem shows up, Android Studio warns me:
suspend function 'yourMethod' should be called only from a coroutine or another suspend function
I'm now freezing here, because the article told me not to use withContext(Dispatchers.IO). And I am now wondering should I use withContext(Dispatchers.Main) or use GlobalScope.launch to run my queries?
So let's say you have a Room DB DAO as below,
@Dao
interface RoomDao
{
@Query("SELECT * FROM $TABLE_NAME WHERE $ITEM_ID = :itemId")
suspend fun getEntry(itemId : String) : Entry?
}
Remember that getEntry is a suspend fun and can only be called inside a suspend fun or inside a coroutineScope or using a runBlocking method(these are how the coroutine framework handles/manages all its work)
So now, there are a few ways to call the above method
1. Use the coroutineScope provided by the fragment, activity or viewModel
Using coroutineScope means that you have launched coroutine(think of it same as Java Thread but lightweight) and are not willing to wait for it. There are two ways to the same launch and async. In terms of way of execution they both does the same thing. They only return different types of handlers to you to modify the launched coroutine. One returns Job by which you can query the coroutine status and cancel it, the other returns the Deffered object by which you can access the returned value of the launched coroutine
This is applicable when you want to access the values returned by the Room DB directly.
The below code demonstrates how to do the same
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// your onViewCreated related code
viewLifecycleOwner.lifecycleScope.launch {
val entry = DbInstance.getDao().getEntry("<yourEntryId>")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
// your onCreate related code
lifecycleScope.launch {
val entry = DbInstance.getDao().getEntry("<yourEntryId>")
}
}
fun launchEntryCall(itemId: String) {
viewModelScope.launch {
val entry = DbInstance.getDao().getEntry("<yourEntryId>")
}
}
GlobalScope(Not recommended)fun launchEntryCall(itemId: String) {
GlobalScope.launch {
val entry = DbInstance.getDao().getEntry("<yourEntryId>")
}
}
GlobalScope is not recommended as you yourself have to manage the lifecycle of the coroutine launched as GlobalScope is not integrated with coroutineScopes provided by the android classes they tend to complicate the code.
Here viewLifecycleOwner.lifecycleScope in fragment and lifecycleScope in activity is provided by the android library(You may need to add those)
2. Call in other suspend fun
This will be helpful to you in cases you want to call multiple similar functions and operate on those to return something meaningful. Let's say you make a function which calls the previous getEntry and checks if the following item is deleted or not from the server and returns the item and null if the item is deleted. You can model the same with below code
suspend fun getItemIfNotDeleted(itemId: String): Entry? {
val entry = DbInstance.getDao().getEntry(itemId)
entry ?: return null
return if (isDeleted(entry.url)) {
null
} else {
entry
}
}
3. Use runBlocking(Not recommended in production code)
runBlocking blocks the execution of the current thread till the coroutine returns or throws an exception. So let's say you want to test the above function a test you can call runBlocking and get the result and assert on it like below code snippet
@Test
fun verifyItemReturnedIsNotNull() {
val entry = runBlocking {
DbInstance.getDao().getEntry("<yourEntryId>")
}
assertNotNull(entry)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With