从ViewModel观察LiveData

时间:2017-11-27 16:51:06

标签: android android-architecture-components android-livedata android-viewmodel

我有一个单独的类来处理数据提取(特别是Firebase),我通常从它返回LiveData对象并异步更新它们。现在我想将返回的数据存储在ViewModel中,但问题是为了获得所述值,我需要观察从我的数据获取类返回的LiveData对象。 observe方法需要一个LifecycleOwner对象作为第一个参数,但我显然没有在我的ViewModel内部,我知道我不应该保持对ViewModel内部的Activity / Fragment的引用。我该怎么办?

8 个答案:

答案 0 :(得分:18)

Google开发者JoseAlcérreca在this blog post中建议在这种情况下使用转换(请参阅“存储库中的LiveData”段落。)

答案 1 :(得分:11)

ViewModel文档中

  

但是,ViewModel对象必须永远不会观察到生命周期感知的可观察对象(例如LiveData对象)的更改。

另一种方法是数据实现RxJava而不是LiveData,然后它不会具有生命周期感知的好处。

todo-mvvm-live-kotlin的Google示例中,它使用ViewModel中没有LiveData的回调。

我猜你是否想要遵守生命周期的整个想法,我们需要在Activity / Fragment中移动观察代码。否则,我们可以在ViewModel中使用回调或RxJava。

另一个折衷方案是在ViewModel中实现MediatorLiveData(或Transformations)并观察(把你的逻辑放在这里)。注意MediatorLiveData观察者不会触发(与转换相同),除非在Activity / Fragment中观察到它。我们所做的是在Activity / Fragment中进行空白观察,其中实际工作实际上是在ViewModel中完成的。

// ViewModel
fun start(id : Long) : LiveData<User>? {
    val liveData = MediatorLiveData<User>()
    liveData.addSource(dataSource.getById(id), Observer {
        if (it != null) {
            // put your logic here
        }
    })
}

// Activity/Fragment
viewModel.start(id)?.observe(this, Observer {
    // blank observe here
})

PS:我读过ViewModels and LiveData: Patterns + AntiPatterns,提出了转换。除非观察到LiveData(可能需要在Activity / Fragment中完成),否则我认为它不起作用。

答案 2 :(得分:6)

使用流程

文档中的指南被误解

<块引用>

但是,ViewModel 对象绝不能观察生命周期感知可观察对象(例如 LiveData 对象)的更改。

在此 Github issue 中,他描述了应用上述规则的情况是,观察到的生命周期感知 observable 由另一个生命周期范围托管。 观察LiveData中的ViewModel包含观察到的LiveData没有问题。

使用流程

class MyViewModel : ViewModel() {
    private val myLiveData = MutableLiveData(1)

    init {
        viewModelScope.launch {
            myLiveData.asFlow().collect {
                // Do Something
            }
        }
    }
}

使用状态流

class MyViewModel : ViewModel() {
    private val myFlow = MutableStateFlow(1)
    private val myLiveData = myFlow.asLiveData(viewModelScope.coroutineContext)
}

PS

asFlow 形成一个流程,使 LiveData 在开始 collect 时激活。我认为使用 MediatorLiveDataTransformations 并附加虚拟观察者的解决方案使用 Flow 没有区别,除了 LiveData 的发射值总是在 {{ 1}} 个实例。

答案 3 :(得分:5)

Use Kotlin coroutines with Architecture components.

您可以使用liveData构建器函数来调用suspend函数,并将结果作为LiveData对象。

val user: LiveData<User> = liveData {
    val data = database.loadUser() // loadUser is a suspend function.
    emit(data)
}

您也可以从块中发出多个值。每次emit()调用都会暂停该块的执行,直到在主线程上设置LiveData的值为止。

val user: LiveData<Result> = liveData {
    emit(Result.loading())
    try {
        emit(Result.success(fetchUser()))
    } catch(ioException: Exception) {
        emit(Result.error(ioException))
    }
}

在gradle配置中,使用androidx.lifecycle:lifecycle-livedata-ktx:2.2.0或更高版本。

还有一个article

更新:也可以在LiveData<YourData> Dao中更改interface。您需要在函数中添加suspend关键字:

@Query("SELECT * FROM the_table")
suspend fun getAll(): List<YourData>

,在ViewModel中,您需要像这样异步获取它:

viewModelScope.launch(Dispatchers.IO) {
    allData = dao.getAll()
    // It's also possible to sync other data here
}

答案 4 :(得分:0)

我认为您可以使用无需生命周期所有者接口的observeForever,并且可以观察来自viewmodel的结果

答案 5 :(得分:0)

我知道已经有关于这个主题的惊人答案,但我也想添加我自己的答案:

如果您想坚持使用 LiveData,您可以始终使用 Transformations.map,这样您就不必在 observe 中使用 ViewModel,而只需在 {{ 1}}/Fragment

否则,您可以使用 Activity,单个事件可观察。有关更多信息,我在这里写了一篇文章:https://coroutinedispatcher.com/posts/decorate_stateful_mvvm/

您不必在 SharedFlow 中传递 viewLifecycleOwner 因为当 ViewModel 只需要时在 observe 中调用 ViewModel 没有意义毕竟是最新的结果。

答案 6 :(得分:0)

距离最初的帖子已经有一段时间了,但我最近偶然发现了同样的问题(也使用 Firebase),我能够通过转换解决它。

我有一个存储库类,其中包含使用 Firebase 的 ValueEventListener 收集的 liveData 对象。 ViewModel 持有对此存储库的引用。

现在,在 ViewModel 中,不再有一个函数从存储库返回 LiveData 值,然后通过观察者将其传递给 Fragment,如下所示:

fun getMyPayments(): LiveData<HashMap<String, Int>> {
    return repository.provideMyPayments()
}

我使用一个带有 Transformations.map 的 val 来表示 LiveData 的最终结果,在被 ViewModel 中的另一个函数处理后:

val myRoomPaymentsList : LiveData<HashMap<String, HashMap<String, Payment>>> = Transformations.map(repository.provideMyPayments()) {data ->
        getRoomPaymentsList(data)
    }

注意第一个参数是你观察到的数据源,第二个参数是你想要得到的结果。 这个 val 是一个 LiveData val,它保存来自存储库的最新值,并根据需要在 Fragment 中提供它,将所有处理保留在 ViewModel 中,仅保留 Framgent 内部的 UI 功能。

然后,在我的 Fragment 中,我在这个 val 上放置了一个观察者:

viewModel.myRoomPaymentsList.observe(viewLifecycleOwner, {
    roomPayments = it
    graphFilterPeriod()
})

答案 7 :(得分:0)

例如,如果您需要获取 ID(作为 LiveData)并使用它再次调用 LiveData。将 ID 存储在 selectedID 中,Transformations 会观察该字段并在它发生变化时调用 getAllByID(selectedID)(也称为 LiveData)。

var selectedID = MutableLiveData<Int>()

val objects = Transformations.switchMap(selectedID) { getAllByID(it) }