【问题标题】:Kotlin coroutine list returning null valueKotlin 协程列表返回空值
【发布时间】:2020-03-12 21:22:00
【问题描述】:

Android 应用程序开发的新手。我正在使用 kotlin,我正在尝试在我的视图模型中从房间数据库中检索一个列表,并在我按下按钮时为我的片段敬酒(下面的代码)。如果我按下按钮一次,我会得到一个空字符串,但如果我按下两次,我会得到列表。我怎样才能一键检索列表?可能我在协程中遗漏了一些东西。

视图模型代码:

var Listado = ""


    fun listaTotal(): String {
        uiScope.launch {
            getTodaListaCompra().forEach{
                Log.i("Listado Compra",Listado )
                Listado = Listado + " " + it
                Log.i("Data",data.value)
                Log.i("Pueba",it)
            }
        }
        return Listado 
    }

片段调用:

Toast.makeText(application, tabListaCompraViewModel.listaTotal(), Toast.LENGTH_SHORT)
                .show()

提前致谢

【问题讨论】:

    标签: android kotlin viewmodel kotlin-coroutines coroutinescope


    【解决方案1】:

    我建议使用 LivaData 来观察数据:

    class MyViewModel : ViewModel() {
    
        val listado: LiveData<String> = MutableLiveData<String>()
    
        fun listaTotal() = viewModelScope.launch {
            var localListado = ""
            getTodaListaCompra().forEach{
                localListado = "$localListado $it"
            }
            (listado as MutableLiveData).value = localListado
        }
    
        // function marked as suspend to suspend a coroutine without blocking the Main Thread
        private suspend fun getTodaListaCompra(): List<String> {
            delay(1000) // simulate request delay
            return listOf("one", "two", "three")
        }
    }
    

    在您的活动或片段中,您可以使用下一个方法来实例化 ViewModel 类并观察数据:

    private fun initViewModel() {
        val viewModel = ViewModelProvider(
                this,
                viewModelFactory { MyViewModel() }
        )[MyViewModel::class.java]
    
        viewModel.listado.observe(this, androidx.lifecycle.Observer { data: String ->
            Toast.makeText(application, data, Toast.LENGTH_SHORT).show()
        })
    
        viewModel.listaTotal()
    }
    
    inline fun <VM : ViewModel> viewModelFactory(crossinline f: () -> VM) = object : ViewModelProvider.Factory {
        @Suppress("UNCHECKED_CAST")
        override fun <T : ViewModel> create(aClass: Class<T>):T = f() as T
    }
    

    您可能还需要导入下一个库:

    api 'androidx.lifecycle:lifecycle-viewmodel-ktx:$LIFECYCLE_VERSION'
    

    【讨论】:

    • 感谢您采用这种方法。我发现真的很有趣,我会在设置完所有内容后尝试。
    【解决方案2】:

    您在开始时定义了一个空字符串。当 listaTotal() 第一次被调用时,会在后台启动一个协程来计算“listado”的值。然而 listaTotal 的返回并没有等待后台协程完成。这就是“listado”仍然为空的原因。

    在您第一次和第二次单击按钮之间,第一个协程完成并且“listado”现在不再为空,因此当您第二次单击该按钮时,协程再次启动但“listado”再次返回在该协程完成之前,它会返回第一个按钮单击的结果。

    因为你只能在主 UI 线程上做一个 Toast,你需要告诉它等待协程完成来获取返回值。您可以使用 runBlocking 来完成,如下所示:

    fun listaTotal(): String = runBlocking {
        getTodaListaCompra().forEach{
            Log.i("Listado Compra",listado )
            listado += " " + it
            Log.i("Data",data.value)
            Log.i("Pueba",it)
        }
        listado
    }
    

    更新: 澄清一下,这种方法会阻塞主 UI 线程,直到返回结果。 因此,您应该考虑使用 LiveData(参见 Sergeys 的回答)或 Flows 来获取数据。这个答案的目的主要是解释你的代码和协程的一般行为。

    【讨论】:

    • @enriqueanaemmagarciacardenas 我猜这不是您所期望的。 runBlocking 正在阻塞当前主线程 kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/…。完全不用协程也能达到同样的效果。如果调用 getTodaListaCompra() 需要很长时间,您的 UI 将冻结。你可以在调用getTodaListaCompra()之前检查这个插入调用delay(5000)函数。
    • 嗨@Sergey,当然,我可以用delay(5000) 达到同样的效果,但是如果我在没有协程的情况下调用getTodaListaCompra(),我会强制关闭,因为我可以阻塞主线程。
    • 嗨@enriqueanaemmagarciacardenas,使用runBlocking 你也阻塞了主线程,正如你在文档中看到的那样:Runs a new coroutine and blocks the current thread interruptibly until its completion。你可以尝试一下: make function private suspend fun getTodaListaCompra(): List&lt;String&gt; { delay(10000) // simulate request delay return listOf("one", "two", "three") } 你会看到你的主线程被阻塞了。使用LiveDatasuspend 函数和withContext(Dispatchers.IO) 不会阻塞主线程。
    • @Sergey:你说的阻塞是对的,我建议也使用 LiveData,甚至更好,Flows。但问题是,他如何检索列表,所以这个答案仍然是正确的。
    猜你喜欢
    • 2018-04-13
    • 2020-12-08
    • 2017-11-08
    • 2019-05-11
    • 1970-01-01
    • 2018-02-22
    • 1970-01-01
    • 1970-01-01
    • 2019-09-24
    相关资源
    最近更新 更多