Why does viewModelScope.launch run on the main thread by default - android

While I was learning coroutines and how to properly use them in an android app I found something I was surprised about.
When launching a coroutine using viewModelScope.launch { } and setting a breakpoint inside the launch lambda I noticed my app wasn't responsive anymore because it was still on the main thread.
This confuses me because the docs of viewModelScope.launch { } clearly state:
Launches a new coroutine without blocking the current thread
Isn't the current thread the main thread ? What is the whole purpose of launch if it doesn't run on a different thread by default ?
I was able to run it on anther thread using viewModelScope.launch(Dispatchers.IO){ } which works as I was expecting, namely on another thread.
What I am trying to accomplish from the launch method is to call a repository and do some IO work namely call a webservice and store the data in a room db. So I was thinking of calling viewModelScope.launch(Dispatchers.IO){ } do all the work on a different thread and in the end update the LiveData result.
viewModelScope.launch(Dispatchers.IO){
liveData.postValue(someRepository.someWork())
}
So my second question is, is this the way to go ?

ViewModelScope.launch { } runs on the main thread, but also gives you the option to run other dispatchers, so you can have UI & Background operations running synchronously.
For you example:
fun thisWillRunOnMainThread() {
viewModelScope.launch {
//below code will run on UI thread.
showLoadingOnUI()
//using withContext() you can run a block of code on different dispatcher
val result = novel.id = withContext(Dispatchers.IO) {
withsomeRepository.someWork()
}
//The below code waits until the above block is executed and the result is set.
liveData.value = result
finishLoadingOnUI()
}
}
For more reference, I would say there are some neat articles that will help you understand this concept.
Medium link that explains it really neat.

So my second question is, is this the way to go ?
I would expect two things to be different in your current approach.
1.) First step would be to define the scheduler of the background operation via withContext.
class SomeRepository {
suspend fun doWork(): SomeResult = withContext(Dispatchers.IO) {
...
}
}
This way, the operation itself runs on a background thread, but you didn't force your original scope to be "off-thread".
2.) Jetpack Lifecycle KTX provides the liveData { coroutine builder so that you don't have to postValue to it manually.
val liveData: LiveData<SomeResult> = liveData {
emit(someRepository.someWork())
}
Which in a ViewModel, you would use like so:
val liveData: LiveData<SomeResult> = liveData(context = viewModelScope.coroutineContext) {
withContext(Dispatchers.IO) {
emit(someRepository.someWork())
}
}
And now you can automatically trigger data-loading via observing, and not having to manually invoke viewModelScope.launch {}.

The idea behind main thread being default is you can run UI operations without having to change the context. It is a convention I guess Kotlin coroutine library writers have chosen
Suppose if by default if the launch runs on IO thread then the code would look like this
viewmodelScope.launch {
val response = networkRequest()
withContext(Dispatchers.Main) {
renderUI(response)
}
}
Suppose if by default if the launch runs on Default thread then the code would look like this
viewmodelScope.launch {
val response: Response = null
withContext(Dispatchers.IO) {
response = networkRequest()
}
withContext(Dispatchers.Main) {
renderUI(response)
}
}
Since the default launch is on main thread, now you have to do below
viewmodelScope.launch {
val response: Response = null
withContext(Dispatchers.IO) {
response = networkRequest()
}
renderUI(response)
}
To avoid the messy code initializing the response with null, we can also make the networkRequest as suspend and wrap the business logic of networkRequest() function in withContext(Dispatchers.IO) and that's how lot of people write their networkRequest() function as well! Hope this makes sense

One of the main reasons it runs on Main thread, is because it's more practical for general use in ViewModel, like murali kurapati wrote. It was a design choice.
It's also important to note that all suspending functions should be "main safe" according to best pracices. That means, that your repository should switch coroutine context accordingly, like so:
class someRepository(private val ioDispatcher: CoroutineDispatcher) {
suspend fun someWork() {
withContext(ioDispatcher) {
TODO("Heavy lifting")
}
}
}

Related

Waiting for coroutine result without blocking main thread

I've to update an item in a fragment based on the data returned from the network call. I don't want to block the main thread and end up with ANR so I adopted the approach of "callbacks" however I wonder if there's a way to wait for the result from the network call without relying on the callback mechanism using coroutines
Current implementation
MyFragment.kt
fun updateButtonText() {
handlerClass.getData {
//received data from the server update the button text
}
}
HandlerClass.kt
fun getData(callback: (String) -> Unit) {
scope.launch(Dispatchers.IO) {
val data = mySuspendedNetworkcallMethod()
callback.invoke(data)
}
}
Desired Implementation:
MyFragment.kt
fun updateButtonText() {
val data = handlerClass.getData()
button.text = data
}
HandlerClass.kt
suspend fun getData() {
return mySuspendedNetworkcallMethod()
}
For the desired demo implementation, I understand, I'd have to use runBlocking{} to call a suspended method however runBlocking{} will block the calling thread - which in this case would be the Main Thread until getData() returns the data.
I don't want to block the main thread but still be able to cal & wait for the suspended method to fetch the data and then update the button.
Coroutines are designed to get rid of callbacks. You can use lifecycleScope in the Fragment class to launch a lifecycle-aware coroutine, it will look like the following:
MyFragment.kt:
fun updateButtonText() = lifecycleScope.launch {
button.text = handlerClass.getData()
}
HandlerClass.kt:
suspend fun getData() {
return mySuspendedNetworkcallMethod()
}
If you use MVVM approach you should consider to use ViewModel and it's viewModelScope extension to launch coroutines.
For LifecycleScope, use androidx.lifecycle:lifecycle-runtime-ktx:2.4.0 or higher.
For ViewModelScope, use androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0 or higher.
Well recommended way is to use viewmodel and viewmodelscope for suspend functions.
However in your situation, just use lifecyclescope
fun updateButtonText() {
lifecycleScope.launch{
val data = handlerClass.getData()
button.text = data
}
}
https://developer.android.com/topic/libraries/architecture/coroutines

Kotlin Android - correct way to dispatch to main thread

I am using OkHttp library to download some data from the internet in my androidx.lifecycle.ViewModel
I then want to update my LiveData. It seems that doing it from background thread throws exception like so:
2022-01-17 15:47:59.589 7354-7396/com.example.myapplication E/AndroidRuntime: FATAL EXCEPTION: OkHttp Dispatcher
Process: com.example.myapplication, PID: 7354
java.lang.IllegalStateException: Cannot invoke setValue on a background thread
at androidx.lifecycle.LiveData.assertMainThread(LiveData.java:487)
at androidx.lifecycle.LiveData.setValue(LiveData.java:306)
at androidx.lifecycle.MutableLiveData.setValue(MutableLiveData.java:50)
at com.example.myapplication.MainActivityViewModel$getOneMoreCat$1.invoke(MainActivityViewModel.kt:86)
at com.example.myapplication.MainActivityViewModel$getOneMoreCat$1.invoke(MainActivityViewModel.kt:39)
at com.example.myapplication.singleton.CommunicationManager$sendRequest$1.onResponse(CommunicationManager.kt:24)
at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:519)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:923)
Now I found two different ways to dispatch to main thread from ViewModel (which has no reference to Context as per AAC guidelines), see here:
GlobalScope.launch {
withContext(Dispatchers.Main) {
// do whatever, e.g. update LiveData
}
}
or
Handler(Looper.getMainLooper()).post(Runnable {
// do whatever, e.g. update LiveData
})
Which is the correct way? That is, least impactful at runtime.
Update I did find that I can also do myLiveData.post() and it works from background thread.
Still, I'd like to know what is the correct way to dispatch work to main thread in modern Android under kotlin
The right way to dispatch work from Background Thread to Main Thread using LivaData is to use LivaData.postValue() method. It posts a task to a main thread to set the given value.
Another approach is to use viewModelScope extension property in ViewModel class, by default it uses Dispatchers.Main context to execute a coroutine, it means you can update UI in such coroutine. For example, in your ViewModel class:
viewModelScope.launch {
val result = makeNetworkCall()
// use result to update UI
liveData.value = result
}
// withContext - switches context to background thread
suspend fun makeNetworkCall(): String = withContext(Dispatchers.IO) {
delay(1000) // simulate network call
"SomeResult"
}
Dependency to use viewModelScope:
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0'
GlobalScope is highly discouraged to use, it can only be used in specific cases, here is a description why not use it.
Inside viewmodel,
private val _downloading = MutableLiveData<Result<Boolean>>()
val downloading: LiveData<Result<Boolean>>
get() = _downloading
fun downloadFile() {
viewModelScope.launch {
try {
_downloading.value = Result.Loading
val result = withContext(Dispatchers.IO) {
// download something
}
_downloading.value = Result.Success(true)
} catch (ex: Exception) {
_downloading.value = Result.Failure(ex)
}
}
}
In activity/fragment,
viewModel.downloading.observe(this, {
when (it) {
is Result.Failure -> TODO()
Result.Loading -> TODO()
is Result.Success -> TODO()
}
})
Result is a sealed class to capture state, which in turn will help us update the UI accordingly. Also viewmodelscope is used instead of GlobalScope since we don't want the download to go on when the viewmodel is destroyed.
there are many ways to do that you can simply post value to live data, using dispatcher's and handler which is running on main thread as you provide looper of main thread.
Another way is you can use high order functions to update the viewmodels which is easy to use and give it a try.

How to use a callback in specific thread in order to wait for it in the main thread?

First question here, I will do my best.
I have a Data class that retrieve a data object with firestore at the creation.
I have done some code to the setters with coroutines. I am not sure of my solution but it is working. However, for the getters, I am struggling to wait the initialisation.
In the initialisation, I have a callback to retrieve the data. The issue that the callback is always called from the main thread, event if I use it in a coroutine in another thread. I check this with:
Log.d("THREAD", "Execution thread1: "+Thread.currentThread().name)
For the setter I use a coroutine in useTask to not block the main thread. And a mutex to block this coroutine until the initialisation in the init is done. Not sure about waitInitialisationSuspend but it is working.
But for the getter, I just want to block the main thread (even if it is bad design, it is a first solution) until the initialisation is done, and resume the getter to retrieve the value.
But I am not enable to block the main thread without also blocking the callback in the initialisation because there are in the same thread.
I have read many documentation about coroutine, scope, runBlocking, thread etc. but everything gets mixed up in my head.
class Story(val id: String) : BaseObservable() {
private val storyRef = StoryHelper.getStoryRef(id)!!
private var isInitialized = false
private val initMutex = Mutex(true)
#get:Bindable
var dbStory: DbStory? = null
init {
storyRef.get().addOnCompleteListener { task ->
if (task.isSuccessful && task.result != null) {
dbStory = snapshot.toObject(DbStory::class.java)!!
if (!isInitialized) {
initMutex.unlock()
isInitialized = true
}
notifyPropertyChanged(BR.dbStory)
}
}
}
fun interface StoryListener {
fun onEvent()
}
private fun useTask(function: (task: Task) -> Unit): Task {
val task = Task()
GlobalScope.launch {
waitInitialisationSuspend()
function(task)
}
return task
}
private suspend fun waitInitialisationSuspend()
{
initMutex.withLock {
// no op wait for unlock mutex
}
}
fun typicalSetFunction(value: String) : Task {
return useTask { task ->
storyRef.update("fieldName", value).addOnSuccessListener {
task.doEvent()
}
}
}
fun typicalGetFunction(): String
{
var result = ""
// want something to wait the callback in the init.
return result
}
}
RunBlocking seems to block the main tread, so I can not use it if the callback still use the main thread.
It is the same problem if I use a while loop in main thread.
#1
runBlocking {
initMutex.withLock {
result = dbStory!!.value
}
}
#2
while (!isInitialized){
}
result = dbStory!!.value
#3
Because maybe the callback in the init is in the main thread also. I have tried to launch this initialisation in a coroutines with a IO dispatcher but without success. The coroutine is well in a different thread but the callback still called in the main thread.
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
scope.launch() {
reference.get().addOnCompleteListener { task ->
In the getter, I have to work with the main thread. The solution is maybe to put the callback execution in another thread but I do not know how to do this. And maybe there is a better solution.
Another solution will be te be able to wait the callback in the main thread without blocking the callback but I have no solution for this.
Any ideas ?
I have loocked for many solutions and the conclusion is, don't do it.
This design is worse than I thougt. Android does not want you to block the main thread even for a short time. Blocking the main thread is blocking all UI and synchronisation mecanism, it is really bad solution.
Even using another thread for the callback (that you can do with an Executor) is, I think, a bad idea here. The good way to wait the end of the task in the callback is to retrieve the task and use:
Tasks.await(initTask)
But it is not allowed in the main thread. Android prevent you to do bad design here.
We should deal with the asynchronous way to manage firebase data base, it is the best way to do that.
I can still use my cache on the data. Here I was waiting to display a dialog with a text I retrieve in firebase. So, I can just display the dialog asynchronously when the text data is retrieved. If the cache is available, it will use it.
Keep also in mind that firebase seems to have some API to use a cache.

Android: kotlin coroutine background task

I have a method
fun refrehList() {
viewModelScope.launch {
myData.value = withContext(Dispatchers.Default) {
summaryRepository.getSummaries(true)
}
allData.value = withContext(Dispatchers.Default) {
SummaryRepository.getSummaries(false)
}
}
}
Is this correct way of using coroutine. Is the DB operation happening in the background scope
If you're using Room, its documentation states the following:
You can add the suspend Kotlin keyword to your DAO methods to make
them asynchronous using Kotlin coroutines functionality. This ensures
that they cannot be executed on the main thread.
So you will be safe calling your repository inside the viewModelScope without changing context.
You can find that Room's documentation section here.
Yes this code will run on separate thread but one after another. Also you should be using Dispatchers.IO for database calls instead of Dispatchers.Default See Io vs Default.
viewModelScope.launch {
myData.value = withContext(Dispatchers.IO) {
Log.e("thread1", Thread.currentThread().name)
summaryRepository.getSummaries(true)
}
Log.e("thread2", Thread.currentThread().name)
allData.value = withContext(Dispatchers.IO) {
Log.e("thread3", Thread.currentThread().name)
SummaryRepository.getSummaries(false)
}
}
This will print something like :-
E/thread: DefaultDispatcher-worker-1
E/thread2: main
E/thread3: DefaultDispatcher-worker-1
If you want to run those co-routine in parallel you can use async-await .

Suspend main thread while calling background thread

Is it possible in Kotlin to call some code from the main thread that blocks until that code completes but at the same time does not block the actual UI? From my understanding, coroutines might be able to do that through a process that "suspends" executing code on the main thread until the other thread completes. But I have no idea how to do that if it is even possible.
Coroutines are pretty easy. Import them:
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.2'
Make your first async code:
private val completableJob = Job()
private val coroutineScope = CoroutineScope(Dispatchers.IO + completableJob)
//you need these because you need to handle them with Android Lifecycle.
fun fireACoroutine(){
corotutineScope.launch(Dispatchers.IO){
val someDataFromSuspendFunction = getStringAfterDelay()
withContext(Dispatchers.Main){
someTextView.text = someDataFromSuspendFunction
//switched to Main Thread.
}
}
var x = 0
x++ //you are outside of a coroutine
}
suspend fun getStringAfterDelay(): String = withContext(Dispatchers.IO){
delay(1000)
"Hello World"
}
Don't forget the to clean your hands when in Activity/Fragment:
override fun onDestroy() {
super.onDestroy()
completableJob.cancel()
}
Done! I also have an article about coroutines: Kotlin Coroutines, the non confusing one
I also suggest to know more about operators like launch, async or runBlocking.
Alternatively to coroutines, you may use RxJava to do what you request in questions. It does work in Kotlin like charm. Also, if you feel like you need to know more about both of them, stick to AsyncTask.

Categories

Resources