Room allowMainThreadQueries with Kotlin coroutines - android

The official Android documentation states that using allowMainThreadQueries() is not recommended because it could lock the UI for a long period of time and trigger an ANR.
But Kotlin coroutines gave us the possibility to perform some operation in the main thread without effectively blocking the UI.
So I'm asking: is it safe to use allowMainThreadQueries() and access the database in a couroutine scope running on the main thread? Like in the following:
// WITH allowMainThreadQueries()
val activityJob = Job()
val mainScope = CoroutineScope(Dispatchers.Main + activityJob)
mainscope.launch {
// access room database and retrieve some data
// update UI with data retrived
}
Or we should stick to the old way of not allowing main thread queries and performing database queries in another thread?
// WITHOUT allowMainThreadQueries()
val activityJob = Job()
val defaultScope = CoroutineScope(Dispatchers.Default + activityJob)
val mainScope = CoroutineScope(Dispatchers.Main + activityJob)
defaultScope.launch {
// access room database and retrieve some data
mainScope.launch {
// update UI with data retrived
}
}
I'm asking because the former way (with allowMainThreadQueries()):
is much more readable (I can update the UI in the same coroutine context of the functions that access the database, without bearing about starting the UI updates in another coroutine scope)
allows for simpler error handling
makes use of only one coroutine scope (so less scopes to care about)

You shouldn't need allowMainThreadQueries() for this to work. A scoped coroutine executs in its thread.
This is what I did not long ago:
#UiThread
fun getUsers(context: Context): LiveData<List<User>> {
if (!::users.isInitialized) {
users = MutableLiveData()
users.postValue(MyDatabase.get(context).users().getAll())
GlobalScope.launch(Dispatchers.Main) {
val usersFromDb: List<User> = async(Dispatchers.IO) {
return#async MyDatabase.get(context).users().getAll()
}.await()
users.value = usersFromDb
}
}
return users
}
You can see this getUsers() method gets called from main thread, returning a LiveData (which is handy in this case). The database query happens in GlobalScope.launch().
So yes, your design is one I personaly like. And one that works. But I don't think that you'll need allowMainThreadQueries() at all. Feel free to read (my) blog post: https://proandroiddev.com/android-viewmodel-livedata-coroutines-contraption-e1e44af690a6

It is recommended to access the database in your ViewModel inside viewmodelScope.
If you are you need to access room database from activity or fragment use
lifecyclescope.launch{ // access database dao functions here which are suspend in definition. }
Or inside the lifecycleScope change your thread using withContext(Dispatchers.IO){}

Related

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>>

Android How to execute flow.map() in viewModel on Dispatchers.Default

I am developing an Android application, using the MVVM design pattern.
So, I have a repository that exposes a flow<T> to a ViewModel. The ViewModel then converts the flow<T> to LiveData<T> using asLiveData() as per the code below.
repository.getFlow().map {
// I will do some long running work here
}.asLiveData()
As you can see, I will do some long-running work in map { }. So I want to execute the code inside map { } in Dispatchers.Default thread.
From my research, I can do with flowOn(Dispatchers.Default) or asLiveData(viewmodelScope.coroutineContext + Dispatchers.Default) as per the code below.
map{} with flowOn(Dispatchers.Default)
repository.getFlow().map {
// I will do some long running work here
}.flowOn(Dispatchers.Default).asLiveData()
map{} with asLiveData(viewmodelScope.coroutineContext + Dispatchers.Default)
repository.getFlow().map {
// I will do some long running work here
}.asLiveData(viewmodelScope.coroutineContext + Dispatchers.Default)
I'd like to understand better about coroutine context, scope and flowOn(). So my questions are as follows
1-1. Does asLiveData(viewmodelScope.coroutineContext + Dispatchers.Default) mean that asLiveData() function will execute in a new coroutine with Dispatchers.Default in viewmodelScope?
1-2. So, then the map {} function executes in the new coroutine with Dispatchers.Default because flow's intermediate operator executes in consumer's coroutine?
I also read flowOn(Dispatchers.Default) executes the upstream flow in the CoroutineContext defined in flowOn() function, which mean that the map{} will execute in Dispatchers.Default thread. Is it fine to use flowOn() in viewmodel with asLiveData()?
Thank you guys
Not sure I understand the question, but basically it means that the flow collection will happen on the passed coroutine context (viewmodelScope + bg thread) and the value will be propagated to the livedata from the bg thread. But the propagation (an emit() call) will always switch to the Main dispatcher.
Yes, when you use flowOn(), all the upstream operators (your map) are run on the defined dispatcher until new flowOn() operator appears. And yes, it is ok to use it with combination with asLiveData().
These two options should be equivalent; I would aim for the clarity, which seems to be better covered with the flowOn() usage.

Is the Kotlin Structured Concurrency [coroutine] model scoped to UI suitable for DB writes?

I'm specifically concerned about inserting user initiated data into the local database.
The following pattern is prevalent in examples (including from official sources, e.g. JetBrains, Google/Android) for using Kotlin coroutines in conjunction with [Android Architecture Components] ViewModels.
class CoroutineScopedViewModel : ViewModel(), CoroutineScope {
private val _job = Job()
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + _job
override fun onCleared() {
super.onCleared()
_job.cancel()
}
fun thisIsCalledFromTheUI() = launch {
/* do some UI stuff on the main thread */
withContext(Dispatchers.IO) {
try {
/* do some IO, e.g. inserting into DB */
} catch (error: IOException) {
/* do some exception handling */
}
}
}
}
It's my understanding of the documentation that in the above example the coroutines started in the UI context (defined through coroutineContext) will be cancelled when the ViewModel is destroyed, but that the code in the withContext(Dispatchers.IO) block will get to run to completion.
But, before I go about refactoring my project from the (pre-1.0.0) globally scoped (launch/async) coroutine model, I feel I need to just have some things clarified:
Is my reading of the documentation correct? Or, will destruction of the viewmodel before the withContext(Dispatchers.IO) block runs to completion trigger cancellation of that job too? I.e. can this model be used for inserting data into my DB, or could some strange timing issue arise where the user hits back or otherwise causes the ViewModel owner to close that ends up losing the data?
I don't want to inadvertently introduce a timing bug because I misunderstood something and therefor converted my code to a model similar to the one shown above.
EDIT:
So, I decided to do a little test, and it seems to me that all those examples using this model to write to the database may have a fundamental bug.
Modifying the code to log what happens, as such:
class ChildViewModel : ViewModel(), CoroutineScope {
private val _job = Job()
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + _job
override fun onCleared() {
super.onCleared()
Log.d("onCleared", "Start")
_job.cancel()
Log.d("onCleared", "End")
}
fun thisIsCalledFromTheUI() = launch {
Log.d("thisIsCalledFromTheUI", "Start")
GlobalScope.launch(Dispatchers.IO) {
Log.d("GlobalScope", "Start")
delay(15000)
Log.d("GlobalScope", "End")
}
withContext(Dispatchers.IO) {
Log.d("withContext", "Start")
delay(10000)
Log.d("withContext", "End")
}
Log.d("thisIsCalledFromTheUI", "End")
}
}
Results in this, if you let it run to completion:
D/thisIsCalledFromTheUI: Start
D/GlobalScope: Start
D/withContext: Start
D/withContext: End
D/thisIsCalledFromTheUI: End
D/GlobalScope: End
But, if you close the Fragment/Activity (not the app) before withContext ends, you get this:
D/thisIsCalledFromTheUI: Start
D/GlobalScope: Start
D/withContext: Start
D/GlobalScope: End
Which indicates, to me at least, that you cannot use this to write non-transient data to the DB.
It's my understanding of the documentation that in the above example the coroutines started in the UI context (defined through coroutineContext) will be cancelled when the ViewModel is destroyed, but that the code in the withContext(Dispatchers.IO) block will get to run to completion.
This isn't a correct reading of the documentation. withContext doesn't start another coroutine, it just changes the current coroutine's context for the duration of its block. Therefore this coroutine will get cancelled, as well as all other coroutines you start without providing a new parent context that has a different job associated with it (or no job at all, like the GlobalScope).
However, your proposed idea to use the GlobalScope for persistent operations is just a local patch for the scenario you're testing, you're still not getting a guarantee it will run to completion. The user can exit the application completely and Android can kill the process.
Therefore, if your goal is building a truly robust application, you must accommodate the fact that, until the coroutine completes, no information was written to the DB. Hopefully you run the operation within a DB transaction that will automatically roll back if your program gets killed, otherwise it will be impossible to prevent inconsistencies.

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.

Room LiveData vs AsyncTask for single query that does not require updates

I'm converting our project to work with Room ORM. It works great when I need a LiveData object updated, and works great for AsyncTasks such as insert, delete, etc., where I do not need a callback. But I'm confused what to use when I need a one-time query that requires a callback. The options are to call AsyncTask to query using the DAO implementation, or LiveData with Observer, and after the first receive, unregister the observer.
I would recommend sticking with the LiveData, particularly if you are using a ViewModel provided by Room. The ArchitectureComponents library really does a great job when it comes to bundling all Room, LiveData and ViewModels together, so try at best to stick with the convention.
The reasons I recommend sticking with LiveData and ViewModels are
ViewModels are Lifecycle aware, meaning they respond appropriately
to Fragment/Activity state changes which would otherwise leave your
AsyncTask either retrieving data for a dead Activity or doing work
when the Activity is no longer there potentially leading to MemoryLeaks
It's best practice (at least for Architecture Components) for a View to observe data/changes to data. If you need just a single callback, unsubscribe after you have received the data. Or use an RxJava single if you are currently using RxJava
If you feel the need to really want to use AsyncTask, I would suggest use an AsyncTaskLoader. This is a more robust/lifecycle aware background thread operation that will cache your data (it is very similar to an AsyncTask so the implementation details won't be too foreign), so if you rotate your device, the data will be cached and is immediately available, and you won't have a memory leak. Also check this Video on loaders by the Android team.
But I advise using the LiveData w/ ViewModels.
I had same issue with Room Implementation. As for LiveData return by room we can observe this on main thread without performing database operation on main thread.
But for fetching Raw object without LiveData we can't do it directly, As database operations on Main thread are not allowed.
I have used LiveData for list of things which I what to keep updated as soon as Database gets Updated. But for some operations I need to fetch it only once and stop observing the database because I know it won't change.
In my case I have used RxJava to Observe the query result only 1 time
Creating Observable for getting User by id from Repository
Observable.create(object : ObservableOnSubscribe<User> {
override fun subscribe(e: ObservableEmitter<User>) {
val user= userRepo.getUserById(currentUserId)
e.onNext(user)
e.onComplete()
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(GetCurrentUserByIdSubscriber())
Repository
fun getUserById(id: Int): User= userDao.getUserById(id)
Dao
#Query("SELECT * FROM users WHERE id= :id")
fun getUserById(id: Int): User
You can see I am returning plain User object instead of LiveData.
In Activity where I want to observe this
private inner class GetCurrentUserByIdSubscriber : Observer<User> {
override fun onSubscribe(d: Disposable) {}
override fun onNext(t: User) {
// update the view after getting the data from database
user_name.text = t.name
}
override fun onComplete() {}
override fun onError(e: Throwable) {}
}
So this is how I can perform one time database fetch on IO thread with RxJava and get callback as soon as object is fetched from the database.
You can call unsubscribe on Subscription return from subscribe() method after results are obtained.
There is simple solution in Transformations method distinctUntilChanged.expose new data only if data was changed.
in case Event behaviour use this:
https://stackoverflow.com/a/55212795/9381524

Categories

Resources