Android multithreading - coroutine and UI thread - android

I am new to multithreading and looking for solution for this problem.
I am launching a method in coroutine which updates data in my database and if it is updated I would like to update the UI for users. How to this? I cannot put runOnUiThread inside a coroutine. Is there some type of magic like -> when coroutine finished -> then -> runOnUi?
Greetings

You don't need to call runOnUiThread as the coroutine will have the main dispatcher as the context.
Let's say you have this helper function to offload work to the I/O thread.
suspend fun <T> withIO(block: suspend CoroutineScope.() -> T) = withContext(Dispatchers.IO, block)
If you are using a ViewModel, then you can call it like this
viewModelScope.launch {
val result = withIO {
// You are on IO thread here.
update your database
}
// The block will be suspended until the above task is done.
// You are on UI thread now.
// Update your UI.
}
If you are not using a ViewModel, you can also use
withContext(Disptachers.Main) {
val result = withIO {
// You are on IO thread
}
// You are back on the main thread with the result from the task
}

Coroutine are task that work on different thread.
What you really want is wating for changes in database. Coroutine in this idea could work for insert data in db, but listening part is role of ViewModel pattern.
I recently answer similar question to yours:
AutocompleteTextView with room
More specific could be this answer from another user:
Wait until Kotlin coroutine finishes in onCreateView()

So the basic problem is to jumping back to main thread after co-routine finishes
this can be done multiple ways
using launch(Dispatcher.Main)
from main thread init co-routine
something like this
//launches coroutine running on main thread
GlobalScope.launch(Dispatchers.Main) {
updateDb()
}
suspend fun updateDb(){
//runs on worker thread and returns data
val value = withContext(Dispatchers.IO){
saveDataInDb();
}
//runs back on main thread
updateUI(value);
}
However global scope should not be used
You can read about that here https://medium.com/#elizarov/the-reason-to-avoid-globalscope-835337445abc
using async await
suspend fun saveInDb() {
val value = GlobalScope.async {
delay(1000)
println("thread running on [${Thread.currentThread().name}]")
10
}
println("value = ${value.await()} thread running on [${Thread.currentThread().name}]")
}
output:
thread running on [DefaultDispatcher-worker-1]
value = 10 thread running on [main]
thread running on [main]

Related

It is safe to request dao flow from UI thread

If we declare a db operation with #Query("..") fun itemList(): List<Item>, we must make sure that the code runs on a background thread.
If we declare it as a suspend function #Query("..") suspend fun itemList(): List<Item>, the code generated by room creates a coroutine and posts it via CoroutinesRoom.execute - which is safe to call on the Main thread
But what about the operations declared with a Flow return type? #Query("..")fun itemList(): Flow<List<Item>> Are they safe to be called on Main thread?
My opinion, looking at the source code generated by room, is that it's not very bad to call that function from UI thread. There is some synchronized code that runs on the calling thread - but the db operation is done on a background thread.
For your case #Query("..")fun itemList(): Flow<List<Item>> room generates flow - it's like observable pattern and you should subscribe to the flow to receive emitted values. You should call to collect function on flow which should be called from coroutines scope and will not blocks main thread(like in your sample with suspend #Query). That's why you should not think about thread just select right scope. Please see sample for android.
Just cold flow object will be created when user call to:
#Query("..")fun observeItemList(): Flow<List<Item>>
on thread from which the method called. To create flow object room generates RoomSQLiteQuery object: just build string representation of statement from #Query("..") and binds query arguments if they are exists(with applying the room's type converters). Then room call CoroutinesRoom.createFlow() method to create a flow object by flow builder . All code of flow builder(bock) will be executed in context of coroutine from which collect method was called.
I wrote a little sample:
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flow
fun main() {
println("main thread: ${Thread.currentThread()}") // main thread.
runBlocking {
println("runBlocking thread: ${Thread.currentThread()}") // main thread too.
launch(Dispatchers.Default) {
println("launch thread create flow: ${Thread.currentThread()}")// DefaultDispatcher thread
val myFlow = observeItemList()
launch (newSingleThreadContext("MyOwnThread")){ // declare context of the coroutine
println("launch collect thread: ${Thread.currentThread()}")// MyOwnThread thread
myFlow.collect {
println("flow collect thread: ${Thread.currentThread()}") // in the context of the calling coroutine (MyOwnThread thread)
println("emited value: $it")
}
}
}
println("some code in main.")
}
}
fun observeItemList() : Flow<Int> {
println("call to observeItemList thread: ${Thread.currentThread()}") // DefaultDispatcher thread
return flow{
println("flow builder thread: ${Thread.currentThread()}") // MyOwnThread thread
repeat(3) {
emit(it)
delay(1000)
}
}
}
Output:
main thread: Thread[main,5,main]
runBlocking thread: Thread[main,5,main]
some code in main.
launch thread create flow: Thread[DefaultDispatcher-worker-1,5,main]
call to observeItemList thread: Thread[DefaultDispatcher-worker-1,5,main]
launch collect thread: Thread[MyOwnThread,5,main]
flow builder thread: Thread[MyOwnThread,5,main]
flow collect thread: Thread[MyOwnThread,5,main]
emited value: 0
flow collect thread: Thread[MyOwnThread,5,main]
emited value: 1
flow collect thread: Thread[MyOwnThread,5,main]
emited value: 2
That why all of code for creating a query statement, arguments binding, creating flow object , collecting emitted items by flow will executed on thread of coroutine context(dispatcher) in which you call:
launch(Right coroutine dispatcher){
// all code works on 'Right coroutine dispatcher'
dao.observeItemList().collect{ }
}
In this case you just get overhead on coroutine creating and starting.
For example if you call to launchIn from some method:
fun test() {
dao.observeItemList().forEach{}.launchIn(scope)
}
Then the flow will be created on test() calling thread but flow's builder body and collecting will be executed on scope's dispatcher.
In this case you get overhead on coroutine creating, starting and creating flow's object (without executing flow's builder block).
If you what to run whatever on a not main thread you should pay any way - do some work on main thread (I mean prepare some objects , starting task in other thread).

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.

When to use withContext?

Currently my code looks like this i have a ViewModel that calls the repository to do some background computations and return a result.
ViewModel function is run with viewModelScope.launch(Dispatchers.IO) and then the repository one is a suspend function.
Do I have to use return withContext{} to ensure that everything will be done sequentially? I checked and it is indeed sequential, but in the documentation i found that it doesn't have to be?
Let's dissect the viewModelScope first from it's source code. It uses a custom implementation of CouroutineScope. The context comprises of a SupervisorJob and a Dispatchers.Main dispatcher. This ensures that the coroutine is launched on the main thread and it's failure doesn't affect other coroutines in the scope.
CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate))
Couple of examples worth exploring.
viewModelScope.launch {
Log.d("ViewModel", "Just viewModelScope: ${Thread.currentThread().name}")
}
// Output: Just viewModelScope: main
viewModelScope.launch(Dispatchers.IO) {
Log.d("ViewModel", "IO viewModelScope: ${Thread.currentThread().name}")
}
// Output: IO viewModelScope: DefaultDispatcher-worker-3
viewModelScope.launch {
Log.d("ViewModel", "viewModelScope thread: ${Thread.currentThread().name}")
withContext(Dispatchers.IO) {
delay(3000)
Log.d("ViewModel", "withContext thread: ${Thread.currentThread().name}")
}
Log.d("ViewModel", "I'm finished!")
}
// Output:
// viewModelScope thread: main
// withContext thread: DefaultDispatcher-worker-4
I checked and it is indeed sequential, but in the documentation i
found that it doesn't have to be.
withContext() is a suspending operation and the coroutine will suspend till it's completion and then proceed ahead. That is apparent from the third example above.
In summary, viewModelScope will use main thread to execute a coroutine whose cancellation won't affect other couroutines. Use withContext when you want to do heavy task off of main thread in a suspending fashion; dispatch it using an appropriate dispatcher. Kotlin Coroutine guide is worth a read.
Edit:
Consider the below code as a single unit of execution. This illustrates the fact that when using withContext(), the caller thread is suspending, but it is not blocked, which allows it to go ahead and pick up some other pending work. The interleaving of the output loggers is of interest to us.
viewModelScope.launch {
Log.d("ViewModel", "viewModelScope thread: ${Thread.currentThread().name}")
withContext(Dispatchers.IO) {
delay(3000)
Log.d("ViewModel", "withContext thread: ${Thread.currentThread().name}")
}
Log.d("ViewModel", "I'm finished!")
}
viewModelScope.launch {
Log.d("ViewModel", "I'm not blocked: ${Thread.currentThread().name}")
}
// Output:
// viewModelScope thread: main
// I'm not blocked: main
// withContext thread: DefaultDispatcher-worker-2
// I'm finished!

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

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")
}
}
}

Switching to UI context in coroutines

I'm new to coroutines and I'm wondering if it's possible to switch from coroutineScope (GlobalScope) to UI scope for the code below. My problem is that the steps inside the coroutine launch body must be executed in a worker thread, otherwise the listener notification must be executed in the ui thread in order to avoid to call runOnUiThread in my activity code.
override suspend fun startRent(name: String, bikeMode: BikeMode, listener: StartRentListener) {
var bleDevice : RxBleDevice
val scanFilter: ScanFilter = ScanFilter.Builder().setDeviceName(name).build()
val scanSettings: ScanSettings = ScanSettings.Builder().build()
val job = GlobalScope.launch {
try {
bleDevice = rxBleClient.scanBleDevicesExt(rxBleClient, scanSettings, scanFilter)
val bleConnection = bleDevice.establishConnectionExt()
// write handshake
connectionManager.writeHandshake(bleDevice, bleConnection)
// open lock
openLock(bleDevice, bikeMode, bleConnection)
// getting user position
apiHelper.sendLockRequest(bleDevice.name, getPosition())
bleDevice.disconnect()
// this should be called on main thread once all the previous operations are finished
listener.onSuccess()
} catch (e: Exception) {
listener.onError(e)
}
}
job.join()
}
A snippet of my current activity code:
bikeAccessClient.startRent(bikeBLEName, BikeMode.HYBRID, object :
StartRentListener {
override fun onSuccess() {
runOnUiThread {
// UI update here
}
}
You may use withContext(Dispatchers.Main) {..} function to execute a part of your code with the other Coroutine Dispatcher.
kotlinx.coroutines.android contains the definition of the Dispatchers.Main function and it integrates correctly with Android UI.
Using explicit Dispatcher in your code is quite error-prone. Instead, I would recommend designing the code with fewer explicit requirements.
I would wrote something like that:
fun uiActionHandlerToStartTheProcess() {
launch(Dispatchers.Main) {
val result = startRent(...) // no callback here, suspend function
//UI Update Here
}
}
suspend fun CoroutineScope.startRent() : SomeResultOfWork {
//that function offloads the execution to a IO (aka brackground) thread
return withContext(Dispatchers.IO){
//here goes your code from `startRent`
//use `suspendCancellableCoroutine {cont -> .. }` if you need to handle callbacks from it
SomeResultOfWork()
}
The code in the launch(Dispatchers.Main){..} block is executed in the UI thread. The call to startRent suspend function suspends the execution in the UI thread. Once the startRent is ready with the reply (from a background thread) it resumes the execution (which is done by the Dispatchers.Main and equivalent to the runOnUiThread {...}) and executes the UI update from the right thread

Categories

Resources