[Android] FlowBinding: Use Coroutine Flow to create responsive UI

Insert picture description here

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-coreIt 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.0the 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 SendChannelFlow, 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:
Insert picture description here

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.

Guess you like

Origin blog.csdn.net/vitaviva/article/details/108901144