【Android】FlowBinding: 使用Coroutine Flow打造响应式UI

在这里插入图片描述

RxBinding


如今的Android开发中越来越多地开始引进MVI、Redux、单向数据流等概念,力求实现像react等前端框架那样的响应式UI开发体验。

除了彻底转向Jetpack Compose那样的激进方案外,客户端也有一些因地制宜的方案,比如RxBinding,通过RxJava与Android View的配合,用Observable替代OnClickListener,从而更高效地实现基于事件驱动的UI开发。

findViewById<Button>(R.id.button).clicks().subscribe {
    
    
    // handle button clicked
}

FlowBinding


kotlinx.coroutines 从1.3 开始增加了Flow库,可以在CoroutineScope中响应式地处理流式数据,堪称协程版的RxJava。相应的出现了很多RxJava=>Flow的优秀项目,今天介绍的FlowBinding就是其中一个:Flow版的RxBinding。

// Platform bindings
implementation "io.github.reactivecircus.flowbinding:flowbinding-android:${flowbinding_version}"
// AndroidX bindings
implementation "io.github.reactivecircus.flowbinding:flowbinding-appcompat:${flowbinding_version}"
implementation "io.github.reactivecircus.flowbinding:flowbinding-core:${flowbinding_version}"
implementation "io.github.reactivecircus.flowbinding:flowbinding-drawerlayout:${flowbinding_version}"
implementation "io.github.reactivecircus.flowbinding:flowbinding-navigation:${flowbinding_version}"
implementation "io.github.reactivecircus.flowbinding:flowbinding-recyclerview:${flowbinding_version}"
implementation "io.github.reactivecircus.flowbinding:flowbinding-swiperefreshlayout:${flowbinding_version}"
implementation "io.github.reactivecircus.flowbinding:flowbinding-viewpager2:${flowbinding_version}"
// Material Components bindings
implementation "io.github.reactivecircus.flowbinding:flowbinding-material:${flowbinding_version}"

除了Android标准控件以为,FlowBinding也支持各种AndroidX中的控件以及Material控件

使用方法

在CoroutineScope中监听OnClick事件:

uiScope.launch {
    
    
    findViewById<Button>(R.id.button)
        .clicks() // this returns a Flow<Unit>
        .collect {
    
    
            // handle button clicked
        }
}

kotlinx-coroutines-core中提供了launchIn(scope) ,可以简化 scope.launch { flow.collect() }的写法:

findViewById<Button>(R.id.button)
    .clicks() // binding API available in flowbinding-android
    .onEach {
    
    
        // handle button clicked
    }
    .launchIn(uiScope)

上面例子中的uiScope代表与Activity/Fragment的Lifecycle一致CoroutineScope,有效避免内存泄漏。

实际开发中可以使用androidx.lifecycle:lifecycle-runtime-ktx:2.2.0 提供的扩展属性LifecycleOwner.lifecycleScope: LifecycleCoroutineScope ,可以在lifecycle的onDestroy的时候,取消其Scope内的协程:

class ExampleActivity : AppCompatActivity() {
    
    
    
    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_example)
        findViewById<Button>(R.id.button)
            .clicks()
            .onEach {
    
    
                // handle button clicked
            }
            .launchIn(lifecycleScope) // provided by lifecycle-runtime-ktx 
            
    }
}

实现原理

实现原理比较简单:

scope.launch {
    
    
    findViewById<Button>(R.id.button)
        .clicks() // this returns a Flow<Unit>
        .collect {
    
    
            // handle button clicked
        }
}

通过callbackFlow,可以将一个callback转成Flow,实现上面的clicks()方法

fun View.clicks(): Flow<Unit> = callbackFlow 
    val listener = View.OnClickListener {
    
    
        offer(Unit)
    }
    setOnClickListener(listener)
    awaitClose {
    
     setOnClickListener(null) }
}

awaitClose{}会在flow结束时执行,所以可以在此处进行反注册
offer()将数据发射到Flow内部使用的SendChannel,但是如果Flow已关闭,可能会抛出异常,所以可以增加对异常的捕获处理

fun <E> SendChannel<E>.safeOffer(value: E) = !isClosedForSend && try {
    
    
    offer(value)
} catch (e: CancellationException) {
    
    
    false
}

整段代码如下:
在这里插入图片描述

最后


FlowBinding借助Coroutine Flow打造了一套响应式UI的工具库,配合LifecycleScope实现自动注销避免内存泄漏。在Kotlin大行其道的今天,建议使用FlowBinding替代RxBinding,并以此为契机发掘更多地使用Flow替代RxJava的使用场景。

猜你喜欢

转载自blog.csdn.net/vitaviva/article/details/108901144