viewModelScope.launch(Dispatchers.IO) purpose - android

In the codeLabs tutorial (Android - Kotlin - Room with a View), they have used "viewModelScope.launch(Dispatchers.IO)" to call insert method. what exactly it is and why is it used for.
Refer the link,
https://codelabs.developers.google.com/codelabs/android-room-with-a-view-kotlin/#8
fun insert(word: Word) = viewModelScope.launch(Dispatchers.IO) {
repository.insert(word)
}

viewModelScope is a CoroutineScope which is tied to your ViewModel. it means that when ViewModel has cleared coroutines inside that scope are canceled too.
Dispatchers.IO means that suspend fun repository.insert(word) will run in IO thread which is managed by kotlin.
there are different Dispachres. Dispatchers.IO is used for IO works like database or remote server. Dispatchers.Default is used for tasks that has high CPU usage. Dispatchers.Main is used for tasks that need to update UI.

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.

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?

Which is better to put the Coroutine call in the Repository or the ViewModel?

Just wondering others opinion I have 2 ways I can go about doing something and was curious which is better (and hopefully why you think so)
I have 2 files WordRepository and WordViewModel. I can either do the coroutines in the Repo or in the ViewModel both ways work, but hoping someone can give me a clue as to why I would do the coroutines in one or the other and vice versa.
Version A.(Where the coroutine is in the Repo)
WordRepo:
class WordRepository(private val wordDao: WordDao): WordRepo {
#WorkerThread
override suspend fun deleteAllLogsOlderThan(XDays: Int): Int = withContext(IO) {
return#withContext wordDao.deleteAll()
}
}
WordViewModel:
class WordViewModel(private val wordRepository: WordRepo) : ViewModel() {
fun deleteAllLogsOlderThanA(XDays:Int): Int = runBlocking {
wordRepository.deleteAllLogsOlderThan(XDays)
}
}
Version B.(Where the coroutine is in the ViewModel)
Word Repo:
class WordRepository(private val wordDao: WordDao): WordRepo {
#WorkerThread
override suspend fun deleteAllLogsOlderThan(XDays: Int): Int = wordDao.deleteAll()
}
WordViewModel:
class WordViewModel(private val wordRepository: WordRepo) : ViewModel() {
fun deleteAllLogsOlderThanA(XDays:Int): Int = runBlocking {
withContext(IO) {
wordRepository.deleteAllLogsOlderThan(XDays)
}
}
}
I think either way is fine honestly but if I have to choose I prefer keeping thread related code in the repository layer. (Version A in the example)
My justification:
(A) View Models should not assume how Repositories internally work. Version B implies that the View Model assumes that the Repository will run on the calling thread.
(B) Also, the repository should not depend on other components to use a specific thread to call its method. The repository should be complete by itself.
(C) To avoid code duplication. Having multiple View models calling one repository method is a very common situation. Version A wins in this case because you only need Corountine in one place, the repository.
I suggest looking at some of the Google Samples to see where they like to handle Coroutine and data access threading codes.
Sunflower uses Coroutine code in their repository. This is probably particularly useful for you because it includes examples of fire-and-forget type of requests.
GitHubBrowserRepo does not use Coroutine but the repository keeps reference to the Executor instances that are used when accessing the data.
The question is where to specify that repository jobs should run on IO threads pools:
A) In Repository
B) In ViewModel
Frankly I am not sure which way is better.
B) Having it in ViewModel means more transparency of what runs on which thread. This is also the way I mostly worked with RxJava. As a negative your ViewModel will be cluttered with thread switching (withContext()), while it's actually obvious that all repository job should run on the background thread. So is this extra information any useful?
A) Having it in Repository means more flexibility regarding thread switching in repository and cleaner code in ViewModel. Code will be less explicit from ViewModel point of view about threads. As a negative all repository methods will need to be suspended, so thus usable only from coroutines.

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.

Categories

Resources