翻译自:
arkadiuszchmura.com/posts/be-ca…
前语
最近我在担任一段代码库,需要在运用 Flow 的 Data 层和仍然依赖 LiveData 暴露 State 数据的 UI 层之间完结桥接。好在 androidx.lifecycle 框架已经提供了一个叫做 asLiveData() 的办法,能够让你毫不费力地将 Flow 转为 LiveData。
但是运用这种方式得到的 LiveData 需要牢记一点:在拥有一个及以上活泼的调查者的条件下,它才会发射数据。倘若上游的 flow 产生了更新,但对应的 LiveData 并非活泼的状况,那么它将无法取得最新的数值。
让我经过如下的实例,向你展现咱们或许会遇到的这种潜在问题。
示例
咱们有一个简单的 Activity,它持有 AAC ViewModel 的实例:
class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
该 ViewModel 的完结是这样的:
class MainViewModel : ViewModel() {
private val repository = Repository()
val state: LiveData<Int> = repository.state.asLiveData()
}
它持有一个 Repository 实例,充任琐碎的数据层。
一起 ViewModel 还经过前面说到的 asLiveData() 办法,将 Repository 持有的 StateFlow 转为了 LiveData 并对外暴露了其 State 数据。
Repository 的完结如下:
class Repository {
private val _state = MutableStateFlow(-1)
val state: StateFlow<Int> = _state
suspend fun update() {
_state.emit(Random.nextInt(until = 1000))
}
}
它拥有一个包裹着 Integer 数据(初始值为 -1)的 StateFlow 示例,一起对外提供了一个办法答应外界更新它的 State:从 0 到 1000 之间取得一个新的随机数。
试想一下,倘若期望 Activity 创立的时分就能履行这个数据更新。咱们能够这么完结:
- 在
MainViewModel内创立一个init()来做这个操作 - Activity 的
onCreate()里调用该办法
// MainViewModel
fun init() {
// update() is suspending, so we launch a new coroutine here
viewModelScope.launch {
repository.update()
}
}
// MainActivity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel.init()
}
这样的话,Activity 创立的时分一个新的协程将被启动,最终会调用 Repository 的 update() ,生成一个随机数并发射到它的 State。
此外,咱们或许还需要在 ViewModel 中去发送包含了重生成数值的事件出去。能够在 ViewModel 中添加一个sendAnalyticalEvent() ,这样能够在履行完 Repository 的 update() 之后当即调用它。
// MainViewModel
fun init() {
viewModelScope.launch {
repository.update()
sendAnalyticalEvent() // <-- NEW
}
}
private fun sendAnalyticalEvent() {
// Typically, we would schedule a network request here
val liveDataValue = state.value
val flowValue = repository.state.value
Log.d("Current number in LiveData", "$liveDataValue")
Log.d("Current number in StateFlow", "$flowValue")
}
该办法内,咱们能够做些典型的操作,比方向后端服务器发送网络恳求。这儿,让咱们仅仅在 Logcat 里打印来自 LiveData and Flow 的数值即可。
上面的运转成果相当出人意料。你或许会争论道:LiveData 没有获取到最新的数值,是由于没有足够的时间从上游的 flow 中搜集数据,不然的话肯定能够拿到正确的数值。
但这个 case 里,不仅仅是 LiveData 取得到的是错误的数值,它取得到的是 null。并且请别忘了,它的存放在 Repository 里的初值是 -1。这只能代表一个意思:这儿的 LiveData 压根没有从 StateFlow 里搜集任何数据。
原因是咱们还没有开端调查这个 LiveData,它天然会被当作对错活泼的。并且依据 asLiveData() 办法的文档能够知道,在这种状况下 LiveData 不会从上游的 flow 搜集任何数据。
asLiveData:Creates a
LiveDatathat has values collected from the originFlow.
上游 flow 数据的搜集发生在
LiveData变成活泼的时分,即LiveData.onActive。假如 flow 没有完结,而LiveData变成了非激活状况,即LiveData.onInactive,那么 flow 的数据搜集将在timeoutInMs参数指定的时间后被取消。除非在超时之前,LiveData变成活泼状况。
一旦咱们开端在 Activity 里调查 LiveData 的数据(因而将促使 LiveData 变成活泼状况),它就能够拥有正确的、最新的数值了。
// MainActivity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel.init()
viewModel.state.observe(this) { // <-- NEW
Log.d("Current number in MainActivity", "$it")
}
}
如下是 Logcat 里新的输出。
上面的示例里,咱们选用的是 StateFlow,但规矩相同适用于 SharedFlow。
并且,状况将愈加糟糕,由于当 LiveData 处于非激活状况的时分,任何发送给 SharedFlow 的事件都将永久丢失(默认状况下 SharedFlow 不会将任何数值从头发送给新的订阅者)。
总结
请时间记住选用 asLiveData() 办法转换 Flow 得到的 LiveData 将会和预期的稍稍不同:它只会在注册了活泼调查者的状况下发射数据。
就我个人而言,这种行为无可厚非:由于咱们都还没有调查它、天然不会在意 LiveData 的数值是啥、能不能获取得到。但话说回来,的确存在一些场景,需要在你没有开端调查的时分,去拜访 ViewModel 中 LiveData 的当时数值。
经过阅读这篇文章,我期望你在遇到这种获取不到正确数值的状况时,不要惊奇、心中有数。


