翻译自:

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 创立的时分就能履行这个数据更新。咱们能够这么完结:

  1. MainViewModel 内创立一个 init() 来做这个操作
  2. 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 的数值即可。

Flow 转 LiveData,数据丢了,肿么回事?

上面的运转成果相当出人意料。你或许会争论道:LiveData 没有获取到最新的数值,是由于没有足够的时间从上游的 flow 中搜集数据,不然的话肯定能够拿到正确的数值。

但这个 case 里,不仅仅是 LiveData 取得到的是错误的数值,它取得到的是 null。并且请别忘了,它的存放在 Repository 里的初值是 -1。这只能代表一个意思:这儿的 LiveData 压根没有从 StateFlow 里搜集任何数据。

原因是咱们还没有开端调查这个 LiveData,它天然会被当作对错活泼的。并且依据 asLiveData() 办法的文档能够知道,在这种状况下 LiveData 不会从上游的 flow 搜集任何数据。

asLiveData:Creates a LiveData that has values collected from the origin Flow.

上游 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 里新的输出。

Flow 转 LiveData,数据丢了,肿么回事?

上面的示例里,咱们选用的是 StateFlow,但规矩相同适用于 SharedFlow

并且,状况将愈加糟糕,由于当 LiveData 处于非激活状况的时分,任何发送给 SharedFlow 的事件都将永久丢失(默认状况下 SharedFlow 不会将任何数值从头发送给新的订阅者)。

总结

请时间记住选用 asLiveData() 办法转换 Flow 得到的 LiveData 将会和预期的稍稍不同:它只会在注册了活泼调查者的状况下发射数据

就我个人而言,这种行为无可厚非:由于咱们都还没有调查它、天然不会在意 LiveData 的数值是啥、能不能获取得到。但话说回来,的确存在一些场景,需要在你没有开端调查的时分,去拜访 ViewModelLiveData 的当时数值。

经过阅读这篇文章,我期望你在遇到这种获取不到正确数值的状况时,不要惊奇、心中有数。