RxBinding
Today's Android development is increasingly introducing concepts such as MVI, Redux, and one-way data flow, striving to achieve a responsive UI development experience like front-end frameworks such as react.
In addition to radical solutions like Jetpack Compose, the client also has some solutions tailored to local conditions, such as RxBinding, through the cooperation of RxJava and Android View, replacing OnClickListener with Observable, so as to achieve event-driven UI development more efficiently.
findViewById<Button>(R.id.button).clicks().subscribe {
// handle button clicked
}
FlowBinding
kotlinx.coroutines has added the Flow library since 1.3, which can process streaming data responsively in CoroutineScope, which can be called the coroutine version of RxJava. Correspondingly, there have been many excellent projects of RxJava=>Flow. The FlowBinding introduced today is one of them: the Flow version of 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}"
In addition to Android standard controls, FlowBinding also supports various controls in AndroidX and Material controls
Instructions
Monitor the OnClick event in CoroutineScope:
uiScope.launch {
findViewById<Button>(R.id.button)
.clicks() // this returns a Flow<Unit>
.collect {
// handle button clicked
}
}
kotlinx-coroutines-core
It is provided launchIn(scope)
to simplify scope.launch { flow.collect() }
the wording:
findViewById<Button>(R.id.button)
.clicks() // binding API available in flowbinding-android
.onEach {
// handle button clicked
}
.launchIn(uiScope)
The uiScope in the above example represents CoroutineScope consistent with the Lifecycle of Activity/Fragment, which effectively avoids memory leaks.
In actual development, you can use androidx.lifecycle:lifecycle-runtime-ktx:2.2.0
the extended attributes provided LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
, and you can cancel the coroutine in the scope of the lifecycle onDestroy:
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
}
}
Implementation principle
The realization principle is relatively simple:
scope.launch {
findViewById<Button>(R.id.button)
.clicks() // this returns a Flow<Unit>
.collect {
// handle button clicked
}
}
Pass callbackFlow
, you can convert a callback to Flow to implement the above clicks()
method
fun View.clicks(): Flow<Unit> = callbackFlow
val listener = View.OnClickListener {
offer(Unit)
}
setOnClickListener(listener)
awaitClose {
setOnClickListener(null) }
}
awaitClose{}
It will be executed at the end of the flow, so you can de-register here to
offer()
transmit the data to the internal use of the SendChannel
Flow, but if the Flow is closed, an exception may be thrown, so the exception capture processing can be increased
fun <E> SendChannel<E>.safeOffer(value: E) = !isClosedForSend && try {
offer(value)
} catch (e: CancellationException) {
false
}
The entire code is as follows:
At last
FlowBinding uses Coroutine Flow to create a set of responsive UI tool libraries, and cooperates with LifecycleScope to realize automatic logout to avoid memory leaks. Today, when Kotlin is popular, it is recommended to use FlowBinding instead of RxBinding, and use this as an opportunity to explore more usage scenarios for using Flow instead of RxJava.