首页 > 解决方案 > 为什么 ViewModel 和 Fragment 中的相同变量不同?

问题描述

我正在尝试按此顺序完成这些步骤。

  1. 用数据填充我的 Room 数据库。
  2. 使用当前数据库内容更新列表变量(在 ViewModel 中)。
  3. 从我的 Fragment 访问列表变量(在 ViewModel 中)。

执行后,我按此顺序获得 Toasts

我期望的是相同数量的元素。是否因为协程而有所不同?我必须使用 LiveData 吗?

神话片段.kt

class MythFragment : Fragment() {

    private lateinit var viewModel: MythViewModel

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_myth, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewModel = ViewModelProviders.of(this).get(MythViewModel::class.java)
        viewModel.populateMyths()
        viewModel.fetchFromDatabase()

        Toast.makeText(activity, "${viewModel.myths.size} in MythFragment", Toast.LENGTH_SHORT)
            .show()
    }
}

MythViewModel.kt

class MythViewModel(application: Application) : BaseViewModel(application) {

    var myths = listOf<Myth>()

    fun populateMyths() {
        launch {
            val dao = MythDatabase(getApplication()).mythDao()

            if (dao.getRowCount() > 0)
                return@launch

            val mythList = listOf<Myth>(
                Myth("This is the myth 1", "This is the evaluation of the myth 1"),
                Myth("This is the myth 2", "This is the evaluation of the myth 2"),
                Myth("This is the myth 3", "This is the evaluation of the myth 3"),
                Myth("This is the myth 4", "This is the evaluation of the myth 4"),
                Myth("This is the myth 5", "This is the evaluation of the myth 5")
            )

            dao.insertAll(
                *mythList.toTypedArray()
            )
        }
    }

    fun fetchFromDatabase() {
        launch {
            myths = MythDatabase(getApplication()).mythDao().getAllMyths()
            Toast.makeText(getApplication(), "${myths.size} in MythViewModel", Toast.LENGTH_SHORT)
                .show()
        }
    }
}

BaseViewModel.kt

abstract class BaseViewModel(application: Application) : AndroidViewModel(application),
    CoroutineScope {

    private val job = Job()

    override val coroutineContext: CoroutineContext
        get() = job + Dispatchers.Main

    override fun onCleared() {
        super.onCleared()
        job.cancel()
    }
}

标签: androidkotlinmvvmandroid-roomkotlin-coroutines

解决方案


我期望的是相同数量的元素。是否因为协程而有所不同?我必须使用 LiveData 吗?

是的,每次使用时,您都会launch在另一个线程上运行一些代码。这就是为什么你的Fragment. 最好使用LiveData,以便您在数据更改时收到通知。

在你的ViewModel你可以做这样的事情:

private val _myths = MutableLiveData<Myth>() // keep the mutable one private
val myths: LiveData<Myth> = _myths // expose an immutable one

fun fetchFromDatabase() {
    launch {
        _myths.postValue(MythDatabase(getApplication()).mythDao().getAllMyths()) 
        Toast.makeText(getApplication(), "${myths.size} in MythViewModel", Toast.LENGTH_SHORT).show()
    }
}

在您的 Fragment 中,您观察到神话的变化:

...
viewModel.populateMyths()
viewModel.fetchFromDatabase()
viewModel.myths.observe(viewLifecycleOwner, Observer { myths ->
    Toast.makeText(activity, "${myths.size} in MythFragment", Toast.LENGTH_SHORT).show()
})

您可以使用构建器使上述代码更简洁livedata(需要androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0or up 依赖项):

val myths = livedata {
    emit(MythDatabase(getApplication()).mythDao().getAllMyths())
}

然后也没有必要再打电话fetchFromDatabase()了。


推荐阅读