12

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.

1
  • You can switch context to Main (withContext(Dispatchers.Main){} Commented Nov 1, 2020 at 2:46

4 Answers 4

6

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"
Sign up to request clarification or add additional context in comments.

Comments

3

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

suspend fun displayDirectoryContents2() {
    ...
    withContext(Dispatchers.Main) {
        textView.text = it.name
    }
    ...
}

Comments

3

The best way to approach this is to first create a function that returns a Flow<String> of filenames that executes itself on the IO dispatcher:

fun File.filenameFlow() = flow<String> { traverseAndEmit(this@filenameFlow) }
        .flowOn(Dispatchers.IO)

private suspend fun FlowCollector<String>.traverseAndEmit(dir: File) {
    dir.listFiles()?.forEach {
        when {
            it.isDirectory -> traverseAndEmit(it)
            it.isFile -> emit(it.name)
        }
    }
}

Now you can simply collect it at any point in the GUI thread without blocking:

File("target").filenameFlow().collect { textView.text = it }

Comments

2

do the scanning of the files off the main thread using the suspend function.

suspend fun loadFiles(): List<String> = withContext(Dispatchers.IO) {
// code for getting the files 
return@withContext list } 

once that's done, update the UI thread using lifecycleScope.launch{ } in onCreate() fun of the activity. As shown below

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

    lifecycleScope.launch { // Dispatchers.Main
        textView.text = loadFiles()[i]
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.