翻译自:
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
LiveData
that 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
的当时数值。
经过阅读这篇文章,我期望你在遇到这种获取不到正确数值的状况时,不要惊奇、心中有数。