Kotlin 与LiveData观察员、适配器、协同程序混淆

Kotlin 与LiveData观察员、适配器、协同程序混淆,kotlin,mvvm,android-livedata,kotlin-coroutines,Kotlin,Mvvm,Android Livedata,Kotlin Coroutines,使用MVVM架构 我在想我应该如何完成这项任务时遇到了困难。我的想法是从RESTAPI检索歌曲列表,将该列表传递到SongListAdapter,SongListAdapter使用歌曲列表创建自定义回收器视图,然后膨胀该视图 在这之前就像 //Get Songs CoroutineScope(IO).launch { val songList = viewModel.getSongs() wit

使用MVVM架构

我在想我应该如何完成这项任务时遇到了困难。我的想法是从RESTAPI检索歌曲列表,将该列表传递到SongListAdapter,SongListAdapter使用歌曲列表创建自定义回收器视图,然后膨胀该视图

在这之前就像

//Get Songs
        CoroutineScope(IO).launch {
            val songList = viewModel.getSongs()                  

            withContext(Main){
                val rvSongs = rvSongs as RecyclerView
                val adapter = SongListAdapter(songList)
                rvSongs.adapter = adapter
                rvSongs.layoutManager = LinearLayoutManager(this@SongListActivity)
            }
        }
但是现在我想用liveData来做这件事,而
val songList=viewModel.getSongs()
并没有切断它。相反,我需要使用

viewModel.getSongs().observe(this@SongListActivity, Observer {
    //UI Stuff
}
问题是,
getSongs()
点击api端点来检索列表,这需要在后台线程上完成。但是不能在后台线程上调用观察器。。。这让我觉得我处理这件事的方式完全错了

我在想我可以有两种不同的功能来获取歌曲。。。一个命中端点,另一个将
列表
数据转换为
可变livedata
,但这感觉太复杂了。我应该如何处理livedata

歌曲列表活动

class SongListActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.song_list) //I changed this for testing, change back to activity_main

        //Set View Model
        val viewModel = ViewModelProvider(this).get(SongListActivityViewModel::class.java)

        //Get Songs
        CoroutineScope(IO).launch{
            //Need to hit getSongs() endpoint here

            withContext(Main){
                val rvSongs = rvSongs as RecyclerView
                val adapter = SongListAdapter(songList)
                rvSongs.adapter = adapter
                rvSongs.layoutManager = LinearLayoutManager(this@SongListActivity)
            }
        }
    }
}
SongListActivityViewModel

class SongListActivityViewModel : ViewModel() {
    private val songLiveData = MutableLiveData<List<Song>?>()

    //Returns list of songs on the first page.
    suspend fun getSongs(): MutableLiveData<List<Song>?> {
        //Hit Endpoint
        val songPage1 = FunkwhaleRepository.getSongs()

        //Store as LiveData
        songLiveData.postValue(songPage1.results)
        return songLiveData
    }
}
class SongListActivityViewModel:ViewModel(){
private val songLiveData=MutableLiveData()
//返回第一页上的歌曲列表。
suspend fun getSongs():MutableLiveData{
//命中端点
val songPage1=funkwalerepository.getSongs()
//存储为LiveData
songLiveData.postValue(songPage1.results)
返回songLiveData
}
}

首先,我认为您应该考虑使用适当的Android生命周期范围来启动您的协同程序。如果您在Android组件中创建了一个作用域,比如
活动
,那么您应该在活动被销毁时自行取消它。有一些很好的文档,我建议大家看一下

关于你的实际问题,你的思路是正确的

首先,您的
ViewModel
应该像在代码中一样公开一些
LiveData
,但应该在其
init
块中启动一个从端点获取数据的协同路由作用域。然后,当它获取数据时,您应该只将
LiveData
值设置为加载的数据

在您的活动中,不要尝试启动协同程序来设置您的
RecyclerView
,只需事先使用
LayoutManager
进行设置,然后开始观察公开的
LiveData
。然后,当观察到的数据发生更改时,才将适配器设置为
RecyclerView

快速的伪代码概述可能有助于:

视图模型

ViewModel {

    val liveData = MutableLiveData<>()

    init {
        scope.launch {
            liveData.value = api.fetch()
        }
    }
}

注意:您也可以使用
liveData(LiveDataScope.(->)Unit)
builder函数简化
ViewModel
代码,但我建议您了解这两种方法以及它们的区别。

很好奇为什么您要从协同程序切换到liveData,当这么多专家建议大家开始从LIveData过渡到协同程序时,我们已经有了SharedFlow。顺便说一句,您的示例协同程序代码中有几个错误。@Tenfour04您能对此进行一点扩展吗?我一直在youtube上关注Mitch的编码,他通常都在谈论在他的旧视频中使用livedata。我是android开发新手,所以我对SharedFlow一无所知。SharedFlow非常新(大约三个月)。它及其子类StateFlow可以用作LiveData的替代品。最近推荐它的原因是它有助于打破您的存储库,并查看模型层对Android特定工具的依赖性。LiveData可能会保留一段时间,因为协同路由和SharedFlow只是Kotlin。但我认为LiveData最终有可能会被弃用,Google会告诉所有Java开发人员,“太糟糕了,换成Kotlin吧。”这让我觉得我应该考虑一下!推荐一些关于这个主题的有用的初学者指南吗?我可以稍后再看并提出一些建议。我想我刚刚从官方文档和Roman Elizarov的一篇中篇文章中了解到。对于
scope
,我建议只使用
viewModelScope
,并将其保存在主调度器上。OP在其原始协同程序代码中的错误在于未使用
lifecycleScope
coroutineScope
,而是创建一个新的作用域,如果抓取超过了活动时间,它将泄漏视图。@Tenfour04我只是给出了一个结构概述,而不是实现细节,所以我不是说“作用域”是字面意义上的,在这种情况下,
viewModelScope
将是正确的选择。这种方法对我很有效!谢谢你给我指点我犯的一些错误@发呆的愤怒没问题!关于
SharedFlow
等内容的简要说明。在这里使用
LiveData
非常合适,只要它位于表示层。然而,如果要实现一个存储库模式来获取数据,那么应该使用
Flow
@HenryTwist的一些实现,我实际上可能正在使用存储库模式。当运行
viewModel.init()
设置livedata时,它会点击
repository.getSongs()
,然后点击
apiInterface.getSongs()
,它使用Refught2实际点击端点。在这种情况下,我应该研究实现流程吗?