2024年最新Kotlin flow实践总结_flow combine(1),头条hr面试

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

        mBinding.tvLoadingStatus.text = "加载中"
    }.onEmpty {
        Log.d(TAG, "coldFlow onEmpty, thread:${Thread.currentThread().name}")
        mBinding.progressBar.isVisible = false
        mBinding.tvLoadingStatus.text = "数据加载为空"
    }.catch {
        Log.d(TAG, "coldFlow catch, thread:${Thread.currentThread().name}")
        mBinding.progressBar.isVisible = false
        mBinding.tvLoadingStatus.text = "数据加载错误:$it"
    }.onCompletion {
        Log.d(TAG, "coldFlow onCompletion, thread:${Thread.currentThread().name}")
        mBinding.progressBar.isVisible = false
        mBinding.tvLoadingStatus.text = "加载完成"
    }
        //指定上游数据流的CoroutineContext,下游数据流不会受到影响
        .flowOn(Dispatchers.Main)
        .collect {
            Log.d(TAG, "coldFlow collect:$it, thread:${Thread.currentThread().name}")
        }
}

}


比如上面的例子。 使用flow构建起函数,创建一个冷流,3秒后发送一个值到数据流中。 使用onStart,onEmpty,catch,onCompletion操作符,监听数据流的状态。


日志输出:



coldFlow onStart, thread:main
coldFlow onCompletion, thread:main
coldFlow collect:1, thread:DefaultDispatcher-worker-1


### 场景二:同一种数据,需要加载本地数据和网络数据


在实际的开发场景中,经常会将一些网络数据保存到本地,下次加载数据的时候,优先使用本地数据,再使用网络数据。  
 但是本地数据和网络数据的加载完成时机不一样,所以可能会有下面几种场景。


1. 本地数据比网络数据先加载完成:那先使用本地数据,再使用网络数据
2. 网络数据比本地数据先加载完成:


* 网络数据加载成功,那只使用网络数据即可,不需要再使用本地数据了。
* 网络数据加载失败,可以继续尝试使用本地数据进行兜底。


3. 本地数据和网络数据都加载失败:通知上层数据加载失败


#### 实现CacheRepositity


将上面的逻辑进行简单封装成一个基类,CacheRepositity。  
 相应的子类,只需要实现两个方法即可。


* CResult:代表加载结果,Success 或者 Error。
* fetchDataFromLocal(),实现本地数据读取的逻辑
* fetchDataFromNetWork(),实现网络数据获取的逻辑



abstract class CacheRepositity {
private val TAG = “CacheRepositity”

fun getData() = channelFlow<CResult<T>> {
    supervisorScope {
        val dataFromLocalDeffer = async {
            fetchDataFromLocal().also {
                Log.d(TAG,"fetchDataFromLocal result:$it , thread:${Thread.currentThread().name}")
                //本地数据加载成功  
                if (it is CResult.Success) {
                    send(it)
                }
            }
        }

        val dataFromNetDeffer = async {
            fetchDataFromNetWork().also {
                Log.d(TAG,"fetchDataFromNetWork result:$it , thread:${Thread.currentThread().name}")
                //网络数据加载成功  
                if (it is CResult.Success) {
                    send(it)
                    //如果网络数据已加载,可以直接取消任务,就不需要处理本地数据了
                    dataFromLocalDeffer.cancel()
                }
            }
        }

        //本地数据和网络数据,都加载失败的情况
        val localData = dataFromLocalDeffer.await()
        val networkData = dataFromNetDeffer.await()
        if (localData is CResult.Error && networkData is CResult.Error) {
            send(CResult.Error(Throwable("load data error")))
        }
    }
}

protected abstract suspend fun fetchDataFromLocal(): CResult<T>

protected abstract suspend fun fetchDataFromNetWork(): CResult<T>

}

sealed class CResult {
data class Success(val data: T) : CResult()
data class Error(val throwable: Throwable) : CResult()
}


#### 测试验证


写个TestRepositity,实现CacheRepositity的抽象方法。  
 通过delay延迟耗时来模拟各种场景,观察日志的输出顺序。



private fun cacheRepositityDemo(){
val repositity=TestRepositity()
lifecycleScope.launch {
repositity.getData().onStart {
Log.d(TAG, “TestRepositity: onStart”)
}.onCompletion {
Log.d(TAG, “TestRepositity: onCompletion”)
}.collect {
Log.d(TAG, “collect: $it”)
}
}
}


##### 本地数据比网络数据加载快



class TestRepositity : CacheRepositity() {
override suspend fun fetchDataFromLocal(): CResult {
delay(1000)
return CResult.Success(“data from fetchDataFromLocal”)
}

override suspend fun fetchDataFromNetWork(): CResult<String> {
    delay(2000)
    return CResult.Success("data from fetchDataFromNetWork")
}

}


模拟数据:本地加载delay1秒,网络加载delay2秒  
 日志输出:collect 执行两次,先收到本地数据,再收到网络数据。



onStart
fetchDataFromLocal result:Success(data=data from fetchDataFromLocal) , thread:main
collect: Success(data=data from fetchDataFromLocal)
fetchDataFromNetWork result:Success(data=data from fetchDataFromNetWork) , thread:main
collect: Success(data=data from fetchDataFromNetWork)
onCompletion


##### 网络数据比本地数据加载快



class TestRepositity : CacheRepositity() {
override suspend fun fetchDataFromLocal(): CResult {
delay(2000)
return CResult.Success(“data from fetchDataFromLocal”)
}

override suspend fun fetchDataFromNetWork(): CResult<String> {
    delay(1000)
    return CResult.Success("data from fetchDataFromNetWork")
}

}


模拟数据:本地加载delay 2秒,网络加载delay 1秒  
 日志输出:collect 只执行1次,只收到网络数据。



onStart
fetchDataFromNetWork result:Success(data=data from fetchDataFromNetWork) , thread:main
collect: Success(data=data from fetchDataFromNetWork)
onCompletion


##### 网络数据加载失败,使用本地数据



class TestRepositity : CacheRepositity() {
override suspend fun fetchDataFromLocal(): CResult {
delay(2000)
return CResult.Success(“data from fetchDataFromLocal”)
}

override suspend fun fetchDataFromNetWork(): CResult<String> {
    delay(1000)
    return CResult.Error(Throwable("fetchDataFromNetWork Error"))
}

}


模拟数据:本地加载delay 2秒,网络数据加载失败  
 日志输出:collect 只执行1次,只收到本地数据。



onStart
fetchDataFromNetWork result:Error(throwable=java.lang.Throwable: fetchDataFromNetWork Error) , thread:main
fetchDataFromLocal result:Success(data=data from fetchDataFromLocal) , thread:main
collect: Success(data=data from fetchDataFromLocal)
onCompletion


##### 网络数据和本地数据都加载失败



class TestRepositity : CacheRepositity() {
override suspend fun fetchDataFromLocal(): CResult {
delay(2000)
return CResult.Error(Throwable(“fetchDataFromLocal Error”))
}

override suspend fun fetchDataFromNetWork(): CResult<String> {
    delay(1000)
    return CResult.Error(Throwable("fetchDataFromNetWork Error"))
}

}


模拟数据:本地数据加载失败,网络数据加载失败  
 日志输出: collect 只执行1次,结果是CResult.Error,代表加载数据失败。



onStart
fetchDataFromNetWork result:Error(throwable=java.lang.Throwable: fetchDataFromNetWork Error) , thread:main
fetchDataFromLocal result:Error(throwable=java.lang.Throwable: fetchDataFromLocal Error) , thread:main
collect: Error(throwable=java.lang.Throwable: load data error)
onCompletion


### 场景三:多种数据源,按照顺序合并进行展示


![Image.png](https://img-blog.csdnimg.cn/img_convert/9abbe46082c8bf48d57b81cb8bf41c1f.png#pic_center)


在实际的开发场景中,经常一个页面的数据,是需要发起多个网络请求之后,组合数据之后再进行显示。 比如类似这种页面,3种数据,需要由3个网络请求获取得到,然后再进行相应的显示。


实现目标:


1. 接口间不需要互相等待,哪些数据先回来,就先展示哪部分
2. 控制数据的显示顺序


##### flow combine操作符


可以合并多个不同的 Flow 数据流,生成一个新的流。 只要其中某个子 Flow 数据流有产生新数据的时候,就会触发 combine 操作,进行重新计算,生成一个新的数据。


##### 例子



class HomeViewModel : ViewModel() {

//暴露给View层的列表数据

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!


img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化的资料的朋友,可以戳这里获取

8910224b259a5ffe804fa6d0db.png)
[外链图片转存中…(img-luROFABf-1715707904369)]
[外链图片转存中…(img-9y1LN3Ol-1715707904369)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化的资料的朋友,可以戳这里获取

### Kotlin Flow Combine Usage In the context of Android development, combining multiple flows is a common requirement when dealing with state management or complex data streams. The `combine` operator allows merging two or more Flows into one single Flow that emits combined results whenever any upstream flow emits new values. The syntax for using `combine` involves passing each source Flow as an argument along with a lambda function defining how these emissions should be transformed together before being sent downstream[^2]. ```kotlin val searchQueryFlow = MutableStateFlow("") val categoryFilterFlow = MutableStateFlow("All") // Combining both flows to create filtered items list based on query and category. val filteredItemsFlow = combine(searchQueryFlow, categoryFilterFlow) { query, category -> repository.getItems().filter { it.name.contains(query, ignoreCase = true) && (category == "All" || it.category == category) } } ``` This code snippet demonstrates creating a combined flow from two separate sources (`searchQueryFlow`, `categoryFilterFlow`). Whenever either changes, this triggers recomputation according to specified logic within provided lambda expression which filters items accordingly. When working inside coroutine scope, ensure proper handling through structured concurrency principles by launching coroutines only where necessary while avoiding potential memory leaks due to unbounded collectors listening indefinitely without cancellation mechanisms in place[^1]. #### Example Implementation Using ViewModelScope To implement such functionality safely within an Android application's architecture component like ViewModels: ```kotlin class ItemViewModel : ViewModel() { private val _query = MutableStateFlow<String>("") private val _category = MutableStateFlow<String>("All") // Expose non-mutable version publicly so UI cannot modify directly but observe instead. val items: StateFlow<List<Item>> get() = _items.asStateFlow() init { viewModelScope.launch { combine(_query, _category) { q, c -> filterItems(q, c) } .collectLatest { itemList -> _items.value = itemList } } } fun updateSearchQuery(newText: String) { _query.value = newText } fun changeCategory(selectedCat: String) { _category.value = selectedCat } private suspend fun filterItems(query: String, cat: String): List<Item> = repository.getItems() .filter { item -> item.name.contains(query, ignoreCase = true) && (cat == "All" || item.category.equals(cat)) } } ``` Here `_query` and `_category` are mutable states representing user inputs; they're used alongside `combine()` method to produce updated lists reflecting current filtering criteria dynamically over time as users interact with interface elements tied back here via corresponding setter methods defined above.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值