在Android中, 我们用到的数据有可能是一次性的, 也有可能是需要多个值的.本文介绍Android中结合协程(coroutines)的MVVM模式如何处理这两种情况. 重点介绍协程 本文被收录在: https://github.com/mengdd/KotlinTutorials One-shot vs multiple values实际应用中要用到的数据可能是一次性获取的(one-shot), 也可能是多个值(multiple values), 或者称为流(stream). 举例, 一个微博应用中:
MVVM构架中的数据类型一次性操作和观察多个值(流)的数据, 在架构上看起来会有什么不同呢?
class MyViewModel { val result = liveData { emit(repository.fetchData()) } } 多个值的实现有两种选择:
可以看出两种方式的主要不同点就是ViewModel消费的数据形式, 是 后面会从ViewModel, Repository和Data source三个层面来说明. Flow是什么既然提到了 Kotlin中的多个值, 可以存储在集合中, 比如list, 也可以靠计算生成sequence, 但如果值是异步生成的, 需要将方法标记为 flow和sequence类似, 但flow是非阻塞的. 看这个例子: fun foo(): Flow<Int> = flow { // flow builder for (i in 1..3) { delay(1000) // pretend we are doing something useful here emit(i) // emit next value } } fun main() = runBlocking<Unit> { // Launch a concurrent coroutine to check if the main thread is blocked launch { for (k in 1..3) { println("I'm not blocked $k") delay(1000) } } // Collect the flow foo().collect { value -> println(value) } } 这段代码执行后输出: I'm not blocked 1 1 I'm not blocked 2 2 I'm not blocked 3 3
如果熟悉Reactive Streams, 或用过RxJava就可以感觉到, Flow的设计看起来很类似. ViewModel层发送单个值的情况比较简单和典型, 这里不再多说, 主要说发送多个值的情况. 每次又分ViewModel消费的类型是 发射N个值LiveData -> LiveDataval currentWeather: LiveData<String> = dataSource.fetchWeather() Flow -> LiveDataval currentWeatherFlow: LiveData<String> = liveData { dataSource.fetchWeatherFlow().collect { emit(it) } } 为了减少boilerplate代码, 简化写法: val currentWeatherFlow: LiveData<String> = dataSource.fetchWeatherFlow().asLiveData() 后面都直接用这种简化的形式了. 发射1+N个值LiveData -> LiveDataval currentWeather: LiveData<String> = liveData { emit(LOADING_STRING) emitSource(dataSource.fetchWeather()) }
Flow -> LiveData用 val currentWeatherFlow: LiveData<String> = liveData { emit(LOADING_STRING) emitSource( dataSource.fetchWeatherFlow().asLiveData() ) } 这样写看起来有点奇怪, 可读性不好, 所以可以利用 val currentWeatherFlow: LiveData<String> = dataSource.fetchWeatherFlow() .onStart{emit(LOADING_STRING)} .asLiveData() Suspend transformation如果想在ViewModel中做一些转换. LiveData -> LiveDataval currentWeatherLiveData: LiveData<String> = dataSource.fetchWeather().switchMap { liveData { emit(heavyTransformation(it)) } } 这里不太适合用 Flow -> LiveData用 val currentWeatherFlow: LiveData<String> = dataSource.fetchWeatherFlow() .map{ heavyTransformation(it) } .asLiveData() Repository层Repository层通常用来组装和转换数据. val currentWeatherFlow: Flow<String> = dataSource.fetchWeatherFlow() .map { ... } .filter { ... } .dropWhile { ... } .combine { ... } .flowOn(Dispatchers.IO) .onCompletion { ... } Data Source层Data Source层是网络和数据库, 通常会用到一些第三方的库.
One-shot operations对于一次性操作比较简单, 数据层的只要 suspend fun doOneShot(param: String) : String = retrofitClient.doSomething(param) 如果所用的网络或者数据库不支持协程, 有办法吗? 答案是肯定的. 比如你用的第三方库是基于callback的, 可以用 suspend fun doOneShot(param: String): Result<String> = suspendCancellableCoroutine { continuation -> api.addOnCompleteListener { result -> continuation.resume(result) }.addOnFailureListener { error -> continuation.resumeWithException(error) }.fetchSomething(param) } 如果协程被取消了, 那么resume会被忽略. 验证代码如期工作后, 可以做进一步的重构, 把这部分抽象出来. Data source with Flow数据层返回 fun fetchWeatherFlow(): Flow<String> = flow { var counter = 0 while(true) { counter++ delay(2000) emit(weatherConditions[counter % weatherConditions.size]) } } 如果你所用的库不支持Flow, 而是用回调, fun flowFrom(api: CallbackBasedApi): Flow<T> = callbackFlow { val callback = object: Callback { override fun onNextValue(value: T) { offer(value) } override fun onApiError(cause: Throwable) { close(cause) } override fun onCompleted() = close() } api.register(callback) awaitClose { api.unregister(callback) } } 可能并不需要LiveData在上面的例子中, ViewModel仍然保持了自己向UI暴露的数据是 lifecycleScope.launchWhenStarted { viewModel.flowToFlow.collect { binding.currentWeather.text = it } } 这样其实和用 参考视频: 文档: 博客: |
|