6 votes

Comment mettre à jour le thread de l'interface utilisateur à partir du thread d'arrière-plan des coroutines ?

J'ai une fonction displayDirectoryContents2(file : File) qui scanne tous les fichiers et vérifie les fichiers et répertoires. Ce que je veux, c'est afficher le chemin du fichier actuel dans une vue de texte dans le fil de l'interface utilisateur.

lateinit var textView: TextView

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

Code de la fonction

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

Je suis novice en matière de coroutines Kotlin. En fait, je veux exécuter la fonction displayDirectoryContents2(file : File) en arrière-plan et mettre à jour le nom du fichier que la fonction est en train de lire dans le thread de l'interface utilisateur, tout comme AsyncTask.

3voto

Dmitri Points 1209

Vous pouvez soit changer de contexte de dispatching (Dispatchers.IO pour la logique, puis Dispatchers.Main pour la mise à jour de l'interface utilisateur), soit déplacer votre code dans un ViewModel et y utiliser la même technique de changement de contexte ou utiliser postvalue() de LiveData. Un exemple de cette dernière méthode est présenté ci-dessous. Vous pouvez lire sur ViewModel ici : 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
}

Puis appelez-le comme ceci à partir de votre activité

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

    }

Dans votre fichier build.gradle, assurez-vous d'importer les dépendances pertinentes

 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"

3voto

Marko Topolnik Points 77257

La meilleure façon d'aborder cette question est de créer d'abord une fonction qui renvoie un fichier Flow<String> de noms de fichiers qui s'exécute lui-même sur le IO répartiteur :

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

Maintenant, vous pouvez simplement collect à n'importe quel moment dans le fil d'exécution de l'interface graphique sans bloquer :

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

2voto

blackr4y Points 251

Vous pouvez faire displayDirectoryContents2 fonction suspendue et ensuite utiliser withContext pour changer de contexte.

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

0voto

waveshaper Points 1190

Effectuer le balayage des fichiers en dehors du thread principal en utilisant la fonction suspend fonction.

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

une fois que c'est fait, mettez à jour le fil de l'interface utilisateur en utilisant lifecycleScope.launch{ } en onCreate() le plaisir de l'activité. Comme indiqué ci-dessous

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

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X