【问题标题】:Confusion with LiveData Observers, Adapters, Coroutines与 LiveData 观察者、适配器、协程混淆
【发布时间】:2021-06-01 13:07:55
【问题描述】:

使用 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>> 但这感觉就像我过于复杂了.我应该如何使用 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
    }
}

【问题讨论】:

  • 很好奇为什么你要从协程切换到 LiveData,既然我们有 SharedFlow,那么很多专家都建议大家开始从 LIveData 过渡到协程。顺便说一句,您的示例协程代码中有几个错误。
  • @Tenfour04 你能扩展一下吗?我一直在 youtube 上关注 codingwithmitch,他通常会在他的旧视频中谈论使用 livedata。我是 android dev 的新手,所以我对 SharedFlow 一无所知。
  • SharedFlow 是相当新的(比如三个月)。它及其子类 StateFlow 可以用作 LiveData 的替代品。这些天被推荐的原因是它有助于打破你的存储库和查看模型层对 Android 特定工具的依赖。 LiveData 可能会保留一段时间,因为协程和 SharedFlow 仅适用于 Kotlin。但我认为 LiveData 最终有可能被弃用,Google 告诉所有 Java 开发人员,“太糟糕了,改用 Kotlin。”。
  • 那感觉像是我应该研究的东西!推荐任何关于该主题的有用的初学者指南?
  • 我可以稍后查看并提出建议。我想我是从官方文档和 Roman Elizarov 的 Medium 帖子中了解到的。

标签: kotlin mvvm android-livedata kotlin-coroutines


【解决方案1】:

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

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

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

在您的活动中,与其尝试启动协程来设置您的RecyclerView,不如预先使用LayoutManager 进行设置,然后开始观察暴露的LiveData。那么当被观察的数据发生变化时,才将适配器设置为RecyclerView

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

视图模型

ViewModel {

    val liveData = MutableLiveData<>()

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

活动

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

注意: 您也可以使用 liveData(LiveDataScope&lt;T&gt;.() -&gt; Unit) 构建器函数来简化 ViewModel 代码,但我建议您了解这两种方法以及它们之间的区别。 p>

【讨论】:

  • 对于scope,我建议简单地使用viewModelScope 并将其保留在主调度程序上。 OP 在其原始协程代码中的错误不是使用lifecycleScopecoroutineScope,而是创建了一个新范围,如果获取时间超过 Activity,则会泄漏视图。
  • @Tenfour04 我只是给出了一个结构概述而不是实现细节,所以我的意思并不是从字面上理解“范围”,在这种情况下viewModelScope 将是正确的选择。跨度>
  • 这种方法对我有用!感谢您对我所犯的一些错误给予指点!
  • @DazedFury 没问题!只是关于SharedFlow 等的提及的快速说明。使用LiveData 非常适合这里,只要它停留在表示层中。而如果您要实现存储库模式来获取数据,那么您应该使用 Flow 的一些实现
  • @HenryTwist 我实际上可能正在使用存储库模式。当viewModel.init() 运行以设置实时数据时,它到达repository.getSongs(),然后到达apiInterface.getSongs(),它使用retrofit2 实际到达端点。在这种情况下我应该考虑实施 Flow 吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-11-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-22
  • 1970-01-01
相关资源
最近更新 更多