Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to update UI thread from coroutines background thread?

I have got a function displayDirectoryContents2(file: File) which scans all the file and check for files and directories. What I want is to display the current file path in a textview in UI thread

lateinit var textView: TextView


GlobalScope.launch(Dispatchers.IO) {
 displayDirectoryContents2(file)
}

Code for the function

private fun displayDirectoryContents2(dir: File?){
    try {
        val files = dir?.listFiles()!!

        files.forEach {

            if (it.isDirectory) {
                displayDirectoryContents2(it)
            } else { 
                   if (it.isFile) {
                    textView.text = it.name // to Update the file name in UI thread
              }
        }


    } catch (e: IOException) {
        e.printStackTrace()
    }
}

I am new to Kotlin Coroutines. Actually I want to run function displayDirectoryContents2(file: File) in background thread and update the name of the file the function is reading in UI thread just like AsyncTask.

like image 938
mad_lad Avatar asked Jan 01 '20 19:01

mad_lad


People also ask

How do I run a thread in the background?

To specify the thread on which to run the action, construct the Handler using a Looper for the thread. A Looper is an object that runs the message loop for an associated thread. Once you've created a Handler , you can then use the post(Runnable) method to run a block of code in the corresponding thread.

Does Android service run in background thread?

Choosing between a service and a threadA service is simply a component that can run in the background, even when the user is not interacting with your application, so you should create a service only if that is what you need.

Does Coroutine work on main thread?

Coroutines were added to Kotlin in version 1.3 and are based on established concepts from other languages. On Android, coroutines help to manage long-running tasks that might otherwise block the main thread and cause your app to become unresponsive.

Why should you avoid to run non UI code on the main thread?

If you put long running work on the UI thread, you can get ANR errors. If you have multiple threads and put long running work on the non-UI threads, those non-UI threads can't inform the user of what is happening.


2 Answers

You can either switch dispatcher contexts (Dispatchers.IO for the logic, then to Dispatchers.Main for updating the UI), or you can move your code into a ViewModel and there use the same context switching technique or use postvalue() of LiveData. An example of doing the latter below. You can read on ViewModel here: https://developer.android.com/topic/libraries/architecture/viewmodel

import androidx.lifecycle.LiveData 
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

class MyViewModel() : ViewModel() {
    private val files: MutableLiveData<List<String>> by lazy {
        MutableLiveData<List<String>>()
    }
    fun loadFiles(path: String) {
        viewModelScope.launch(){
            doLoadFiles()
        }
    }
    private suspend fun doLoadFiles() {
        withContext(Dispatchers.IO) {
            val results = listOf("patha", "pathb")//replace with your actual code
            files.postValue(results)
        }
    }
    fun getFiles(): LiveData<List<String>> = files
}

Then call it like this from your activity

import androidx.appcompat.app.AppCompatActivity
import android.view.Menu
import android.view.MenuItem
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders

import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val model = ViewModelProviders.of(this)[MyViewModel::class.java]
        model.getFiles().observe(this, Observer<List<String>>{ paths ->
            // update UI
            println (paths)
        })
        model.loadFiles("S")

    }

In your build.gradle file, make sure to import the relevant dependencies

 def lifecycle_ver = "2.2.0-rc02"
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_ver"
    implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_ver"
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_ver"
like image 158
Dmitri Avatar answered Sep 19 '22 21:09

Dmitri


You can make displayDirectoryContents2 suspended function and then use withContext to switch context.

suspend fun displayDirectoryContents2() {
    ...
    withContext(Dispatchers.Main) {
        textView.text = it.name
    }
    ...
}
like image 34
blackr4y Avatar answered Sep 19 '22 21:09

blackr4y