如何使用协程Flow定义链式调用DSL

本文介绍了一种通过定义DSL(领域特定语言)来控制异步加载流程的方法,包括启动Flow、变换函数和订阅函数的定义,以及如何在实际场景中应用这些函数。同时,考虑到Flow的实验性质,还提供了使用Rx作为替代方案的实现方式。

定义DSL

定义启动flow的函数:


    protected val _spinner: MutableLiveData<Boolean> by lazy { MutableLiveData<Boolean>() }
    val spinner: LiveData<Boolean>
        get() = _spinner
        
    fun <T> loadAsync(showSpinner: Boolean = true, block: suspend () -> T): Flow<T> {
        if (showSpinner) {
            _spinner.value = true
        }
        return block.asFlow()
    }

定义变换函数:

    fun <T, R> Flow<T>.then(block: suspend (T) -> R): Flow<R> {
        return this.flatMapConcat { flow { block(it) } }
    }

定义订阅函数:


 protected val uiScope = CoroutineScope(Dispatchers.main
                    + viewModelJob
                    + CoroutineExceptionHandler { _, exception ->
                		Logger.e("caught original $exception")})
                		
 fun <T> Flow<T>.result(success: (T) -> Unit,
                        error: (Throwable) -> Unit = { _errorToast.value = it.message }) {
        uiScope.launch {
            this@result.flowOn(Dispatchers.io)
                    .catch { cause ->
                        val err = cause as? AccessThrowable
                        when (err?.code) {
                            ERROR.ERROR_NETWORK -> {
                                _networkError.postValue(err)
                            }
                            ERROR.ERROR_TOKEN_EXPIRED -> {
                                _reLogin.postValue(true)
                            }
                        }
                        if (err == null) {
                            error(AccessThrowable(ERROR.UNKNOWN, cause.message ?: ""))
                        } else {
                            error(err)
                        }
                    }.onCompletion {
                        if (_spinner.value == true) {
                            _spinner.value = false
                        }
                    }.collect {
                        success.invoke(it)
                    }
        }
    }

如何使用?

fun function(...parameters...) {
        loadAsync {
        	//suspend function,which returns bool
            repo.function1(...parameters...)
        }.then {
            if (it) {
                //suspend function
                repo.function2(...parameters..)
            } else throw AccessThrowable(ERROR.UNKNOWN, "失败")
        }.result({
            _value.postSuccess(it)
        }, {
            _value.postError(it)
        })
    }

由于Flow属于实验性质的,暂时用Rx代替,这需要使用coroutines的Rx扩展包

定义启动flow的函数:

                		
    protected val _spinner: MutableLiveData<Boolean> by lazy { MutableLiveData<Boolean>() }
    val spinner: LiveData<Boolean>
        get() = _spinner
        
    fun <T : Any> loadAsync(showSpinner: Boolean = true,
                            block: suspend CoroutineScope.() -> T): Single<T> {
        if (showSpinner) {
            _spinner.value = true
        }
        return rxSingle(block = block)
    }

定义变换函数:

    fun <T : Any, R : Any> Single<T>.then(block: suspend CoroutineScope.(T) -> R): Single<R> {
        return flatMap {
            rxSingle { block(it) }
        }
    }

定义订阅函数:

    fun <T> Single<T>.result(success: (T) -> Unit,
                           error: (AccessThrowable) -> Unit = { _errorToast.value = it.message }) {
        disposable = subscribeOn(schedulers.io)
                .observeOn(schedulers.main)
                .subscribe({
                    if (_spinner.value == true) {
                        _spinner.value = false
                    }
                    success.invoke(it)
                },{
                    if (_spinner.value == true) {
                        _spinner.value = false
                    }
                    (it as? AccessThrowable)?.run { error.invoke(this) }
                })
    }
在 Kotlin 协程中,`withContext` 是一个非常重要的函数,用于在不改变协程的生命周期或取消行为的前提下切换执行上下文。它通常用于在不同 `Dispatcher` 之间切换线程,例如从主线程切换到后台线程执行耗时任务,然后再返回主线程更新 UI。 ### 使用场景与方式 `withContext` 的典型用法是在 `Flow` 或普通协程体中执行阻塞或计算密集型操作时避免阻塞主线程。例如: ```kotlin val result = withContext(Dispatchers.IO) { // 执行网络请求、数据库查询等操作 fetchDataFromNetwork() } ``` 在 `Flow` 中,虽然 `flow` 构建器本身支持使用 `flowOn` 来指定上游操作的上下文,但在某些情况下仍然需要显式使用 `withContext` 来控制特定代码块的执行线程[^1]。 ### 最佳实践 #### 1. **避免滥用** - 在 Flow 中优先使用 `flowOn` 而不是 `withContext`:`flowOn` 更适合用于声明整个流链的上下文切换,而 `withContext` 更适合于局部代码块的上下文切换。 - 如果你在一个 `Flow` 操作符内部执行耗时任务,可以考虑将整个流链通过 `flowOn(Dispatchers.IO)` 指定执行上下文,而不是在每个中间处理步骤都使用 `withContext` [^1]。 #### 2. **选择合适的 Dispatcher** - 根据任务类型选择合适的 `CoroutineDispatcher`: - `Dispatchers.Main`:用于 UI 更新和与主线程交互的操作。 - `Dispatchers.IO`:适用于 IO 密集型任务,如文件读写、网络请求。 - `Dispatchers.Default`:适用于 CPU 密集型任务,如数据转换、图像处理。 #### 3. **性能优化** - 避免频繁地切换上下文。如果多个操作可以共享相同的上下文,则应合并它们以减少上下文切换带来的开销。 - 注意不要在 `withContext` 内部执行可能引发内存泄漏的操作,尤其是在 Android 开发中涉及 Activity 或 Fragment 的引用时。 #### 4. **取消与异常处理** - `withContext` 块会继承当前协程的取消状态。如果在其执行过程中协程被取消,`withContext` 将抛出 `CancellationException`。 - 确保在 `try-catch` 块中捕获异常以防止协程意外终止,尤其是在执行不可靠的外部调用(如网络请求)时。 示例代码如下: ```kotlin viewModelScope.launch { val data = try { withContext(Dispatchers.IO) { // 模拟网络请求 delay(1000) "Data from network" } } catch (e: Exception) { // 处理异常 "Error" } // 更新 UI textView.text = data } ``` #### 5. **结合 Flow 使用** - 在 `Flow` 中使用 `withContext` 时,需注意其作用范围仅限于该代码块,不会影响后续操作符的执行上下文。若希望整个流链都在某个上下文中运行,建议使用 `flowOn`: ```kotlin flow { emit(fetchData()) // 在当前上下文中执行 } .map { withContext(Dispatchers.Default) { // 数据处理逻辑 processData(it) } } .flowOn(Dispatchers.IO) .launchAndCollectIn(lifecycleScope, Lifecycle.State.STARTED) ``` 在这个例子中,`fetchData()` 在 `Dispatchers.IO` 上运行,而 `processData` 则在 `Dispatchers.Default` 上运行,展示了如何灵活组合不同上下文以实现高效的并发处理。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值