与 LiveData Observers、Adapters、Coroutines 混淆

Confusion with LiveData Observers, Adapters, Coroutines

使用 MVVM 架构。

我无法弄清楚我应该如何完成这项任务。我的想法是,我想从 REST API 检索歌曲列表,将该列表传递到使用歌曲列表创建自定义回收器视图的 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 端点以检索列表,这需要在后台线程上完成。但是不能在后台线程上调用观察者......这让我觉得我处理这个问题的方式是错误的。

我在想我可以有两个不同的函数来获取歌曲...一个到达终点,另一个将 List<Song> 数据转换为 MutableLiveData<List<Song>> 但感觉就像我太复杂了。我应该如何使用实时数据执行此操作?

歌曲列表活动

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

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

关于你的实际问题,你是在正确的轨道上!

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

在您的 activity 中,与其尝试启动协程来设置您的 RecyclerView,不如仅使用 LayoutManager 预先设置它,然后开始观察暴露的 [=14] =].然后当被观察的数据发生变化时,才将适配器设置为 RecyclerView.

快速伪代码概述可能会有所帮助:

ViewModel

ViewModel {

    val liveData = MutableLiveData<>()

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

Activity

Activity {
    onCreate {
        ...
        recyclerView.layoutManager = LinearLayoutManager
        ...
        viewModel.liveData.observe {
            recyclerView.adapter = Adapter(data)
        }
    }
}

注意: 您也可以使用 liveData(LiveDataScope<T>.() -> Unit) 构建器函数来简化 ViewModel 代码,但我建议您了解这两种方法以及它们有何不同。