将多个挂起函数映射到单个 LiveData
Map multiple suspend functions to single LiveData
我刚开始工作的公司使用所谓的 Navigator,我现在将其解释为无状态 ViewModel。我的导航器收到一些用例,每个用例都包含 1 个挂起函数。任何这些用例的结果都可能以单个 LiveData 结束。 Navigator 没有协程作用域,因此我使用 fetchValue()
.
将作用域暂停的责任传递给 Fragment
项目中的大多数当前代码在数据层中都有 LiveData,我尽量不这样做。正因为如此,他们的 livedata 是从 view 到 dao 的 linked。
我的简化版类:
class MyFeatureNavigator(
getUrl1: getUrl1UseCase,
getUrl1: getUrl1UseCase
) {
val url = MediatorLiveData<String>()
fun goToUrl1() {
url.fetchValue { getUrl1() }
}
fun goToUrl2() {
url.fetchValue { getUrl2() }
}
fun <T> MediatorLiveData<T>.fetchValue(provideValue: suspend () -> T) {
val liveData = liveData { emit(provideValue()) }
addSource(liveData) {
removeSource(liveData)
value = it
}
}
}
class MyFeatureFragment : Fragment {
val viewModel: MyFeatureViewModel by viewModel()
val navigator: MyFeatureNavigator by inject()
fun onViewCreated() {
button.setOnClickListener { navigator.goToUrl1() }
navigator.url.observe(viewLifecycleOwner, Observer { url ->
openUrl(url)
})
}
}
我的两个问题:
fetchValue()
是 link LiveData 暂停功能的好方法吗?能不能漏还有其他问题吗?
- 我在数据层中仅使用协程(和流)的主要原因是 'because Google said so'。还有什么更好的理由呢?并且:与项目和当前的良好编码实践保持一致的最佳权衡是什么?
- fetchValue() 是 link LiveData 挂起函数的好方法吗?
能不能漏还有其他问题吗?
通常它应该可以工作。您可能应该在添加新的之前删除 MediatorLiveData
的先前来源,否则如果您连续两次调用 fetchValue
,第一个 url 的获取速度可能较慢,因此它稍后会赢。
我没有看到任何其他正确性问题,但这段代码非常复杂,创建了几个中间对象并且通常难以阅读。
- 我在数据层只使用协程(和流)的主要原因,
是 'because Google said so'。什么是更好的理由?
Google 提供了很多有用的扩展来在 UI 层中使用协程,例如看看 this page。所以很明显他们鼓励人们使用它。
您的意思可能是建议在 UI 层中使用 LiveData
而不是 Flow
。这不是一个严格的规则,它有一个原因:LiveData
是一个价值持有者,它保留其价值并立即将其提供给新订阅者而不做任何工作。这在 UI/ViewModel 层中特别有用 - 当发生配置更改并重新创建 activity/fragment 时,新创建的 activity/fragment 使用相同的视图模型,订阅相同的 LiveData
和免费获得价值。
同时 Flow
是 'cold',如果您从视图模型中公开一个流,则每次重新配置都会触发一个新的流集合,并且该流将从头开始执行。
例如如果您从数据库或网络获取数据,LiveData
将只向新订阅者提供最后一个值,而 Flow
将再次执行昂贵的 db/network 操作。
正如我所说,没有严格的规则,这取决于特定的用例。此外,我发现在视图模型中使用 Flow
非常有用 - 它提供了很多运算符并使代码简洁明了。但是我借助 asLiveData()
之类的扩展将其转换为 LiveData
,并将此 LiveData
公开给 UI。通过这种方式,我从这两个词中得到了最好的结果——LiveData
在重新配置之间捕获价值,Flow
使视图模型的代码漂亮干净。
您也可以经常使用最新的 StateFlow
和 SharedFlow
它们也可以帮助克服 UI 层中提到的 Flow
问题。
回到你的代码,我会这样实现它:
class MyFeatureNavigator(
getUrl1: getUrl1UseCase,
getUrl1: getUrl1UseCase
) {
private val currentUseCase = MutableStateFlow<UseCase?>(null)
val url = currentUseCase.filterNotNull().mapLatest { source -> source.getData()}.asLiveData()
fun goToUrl1() {
currentUseCase.value = getUrl1
}
fun goToUrl2() {
currentUseCase.value = getUrl2
}
}
这样就没有竞争条件需要关心,代码也很干净。
- 并且:与项目保持一致的最佳权衡是什么
和当前的良好编码实践?
这是一个有争议的问题,应该主要由团队决定。在我参与的大多数项目中,我们都采用了这样的规则:在修复错误时,在维护现有代码时,应该遵循相同的风格。在开发大型 refactoring/implementing 新功能时,应该使用团队采用的最新做法。
我刚开始工作的公司使用所谓的 Navigator,我现在将其解释为无状态 ViewModel。我的导航器收到一些用例,每个用例都包含 1 个挂起函数。任何这些用例的结果都可能以单个 LiveData 结束。 Navigator 没有协程作用域,因此我使用 fetchValue()
.
项目中的大多数当前代码在数据层中都有 LiveData,我尽量不这样做。正因为如此,他们的 livedata 是从 view 到 dao 的 linked。
我的简化版类:
class MyFeatureNavigator(
getUrl1: getUrl1UseCase,
getUrl1: getUrl1UseCase
) {
val url = MediatorLiveData<String>()
fun goToUrl1() {
url.fetchValue { getUrl1() }
}
fun goToUrl2() {
url.fetchValue { getUrl2() }
}
fun <T> MediatorLiveData<T>.fetchValue(provideValue: suspend () -> T) {
val liveData = liveData { emit(provideValue()) }
addSource(liveData) {
removeSource(liveData)
value = it
}
}
}
class MyFeatureFragment : Fragment {
val viewModel: MyFeatureViewModel by viewModel()
val navigator: MyFeatureNavigator by inject()
fun onViewCreated() {
button.setOnClickListener { navigator.goToUrl1() }
navigator.url.observe(viewLifecycleOwner, Observer { url ->
openUrl(url)
})
}
}
我的两个问题:
fetchValue()
是 link LiveData 暂停功能的好方法吗?能不能漏还有其他问题吗?- 我在数据层中仅使用协程(和流)的主要原因是 'because Google said so'。还有什么更好的理由呢?并且:与项目和当前的良好编码实践保持一致的最佳权衡是什么?
- fetchValue() 是 link LiveData 挂起函数的好方法吗? 能不能漏还有其他问题吗?
通常它应该可以工作。您可能应该在添加新的之前删除 MediatorLiveData
的先前来源,否则如果您连续两次调用 fetchValue
,第一个 url 的获取速度可能较慢,因此它稍后会赢。
我没有看到任何其他正确性问题,但这段代码非常复杂,创建了几个中间对象并且通常难以阅读。
- 我在数据层只使用协程(和流)的主要原因, 是 'because Google said so'。什么是更好的理由?
Google 提供了很多有用的扩展来在 UI 层中使用协程,例如看看 this page。所以很明显他们鼓励人们使用它。
您的意思可能是建议在 UI 层中使用 LiveData
而不是 Flow
。这不是一个严格的规则,它有一个原因:LiveData
是一个价值持有者,它保留其价值并立即将其提供给新订阅者而不做任何工作。这在 UI/ViewModel 层中特别有用 - 当发生配置更改并重新创建 activity/fragment 时,新创建的 activity/fragment 使用相同的视图模型,订阅相同的 LiveData
和免费获得价值。
同时 Flow
是 'cold',如果您从视图模型中公开一个流,则每次重新配置都会触发一个新的流集合,并且该流将从头开始执行。
例如如果您从数据库或网络获取数据,LiveData
将只向新订阅者提供最后一个值,而 Flow
将再次执行昂贵的 db/network 操作。
正如我所说,没有严格的规则,这取决于特定的用例。此外,我发现在视图模型中使用 Flow
非常有用 - 它提供了很多运算符并使代码简洁明了。但是我借助 asLiveData()
之类的扩展将其转换为 LiveData
,并将此 LiveData
公开给 UI。通过这种方式,我从这两个词中得到了最好的结果——LiveData
在重新配置之间捕获价值,Flow
使视图模型的代码漂亮干净。
您也可以经常使用最新的 StateFlow
和 SharedFlow
它们也可以帮助克服 UI 层中提到的 Flow
问题。
回到你的代码,我会这样实现它:
class MyFeatureNavigator(
getUrl1: getUrl1UseCase,
getUrl1: getUrl1UseCase
) {
private val currentUseCase = MutableStateFlow<UseCase?>(null)
val url = currentUseCase.filterNotNull().mapLatest { source -> source.getData()}.asLiveData()
fun goToUrl1() {
currentUseCase.value = getUrl1
}
fun goToUrl2() {
currentUseCase.value = getUrl2
}
}
这样就没有竞争条件需要关心,代码也很干净。
- 并且:与项目保持一致的最佳权衡是什么 和当前的良好编码实践?
这是一个有争议的问题,应该主要由团队决定。在我参与的大多数项目中,我们都采用了这样的规则:在修复错误时,在维护现有代码时,应该遵循相同的风格。在开发大型 refactoring/implementing 新功能时,应该使用团队采用的最新做法。