Corbind
⚡ Kotlin Coroutines binding APIs for Android UI widgets from the platform, AndroidX, and Material libraries. Use cold Flow bindings for new code; hot ReceiveChannel and actor-based action overloads remain available for existing integrations.
Corbind turns Android UI callbacks into cold Flow bindings or hot ReceiveChannel bindings. The action overloads use the actor coroutine API, which is marked obsolete upstream. Please consider giving this repository a star ⭐ if you like the project.
Articles
- ⚡ Kotlin Coroutine binding with Flow support
- What’s up Corbind! Release 1.7.0 🎉. It’s been a long road
Current versions
Using in your projects
Platform bindings:
dependencies {
implementation(platform("ru.ldralighieri.corbind:corbind-bom:2026.09.00"))
implementation("ru.ldralighieri.corbind:corbind")
}
AndroidX library bindings:
dependencies {
implementation(platform("ru.ldralighieri.corbind:corbind-bom:2026.09.00"))
implementation("ru.ldralighieri.corbind:corbind-activity")
implementation("ru.ldralighieri.corbind:corbind-appcompat")
implementation("ru.ldralighieri.corbind:corbind-core")
implementation("ru.ldralighieri.corbind:corbind-drawerlayout")
implementation("ru.ldralighieri.corbind:corbind-fragment")
implementation("ru.ldralighieri.corbind:corbind-leanback")
implementation("ru.ldralighieri.corbind:corbind-lifecycle")
implementation("ru.ldralighieri.corbind:corbind-navigation")
implementation("ru.ldralighieri.corbind:corbind-recyclerview")
implementation("ru.ldralighieri.corbind:corbind-slidingpanelayout")
implementation("ru.ldralighieri.corbind:corbind-swiperefreshlayout")
implementation("ru.ldralighieri.corbind:corbind-viewpager") // legacy
implementation("ru.ldralighieri.corbind:corbind-viewpager2")
}
Use corbind-viewpager2 for new screens. The corbind-viewpager module supports the older androidx.viewpager.widget.ViewPager; see the ViewPager2 migration guide.
Google 'material' library bindings:
dependencies {
implementation(platform("ru.ldralighieri.corbind:corbind-bom:2026.09.00"))
implementation("ru.ldralighieri.corbind:corbind-material")
}
Snapshot build:
repositories {
maven("https://central.sonatype.com/repository/maven-snapshots/")
}
dependencies {
implementation(platform("ru.ldralighieri.corbind:corbind-bom:2026.10.00-SNAPSHOT"))
implementation("ru.ldralighieri.corbind:{module}")
}
List of extensions
You can find a list of extensions in the description of each module:
- corbind
- corbind-activity
- corbind-appcompat
- corbind-core
- corbind-drawerlayout
- corbind-fragment
- corbind-leanback
- corbind-lifecycle
- corbind-material
- corbind-navigation
- corbind-recyclerview
- corbind-slidingpanelayout
- corbind-swiperefreshlayout
- corbind-viewpager (legacy)
- corbind-viewpager2
How to use it?
For one cold Flow, flowWithLifecycle restarts collection when the Activity reaches STARTED. textChanges() returns InitialValueFlow, so each new collection begins with the current text:
findViewById<EditText>(R.id.etName)
.textChanges()
.onEach { text -> /* handle the current text and later changes */ }
.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
.launchIn(lifecycleScope)
Use repeatOnLifecycle to scope collection explicitly, especially when collecting several flows in parallel (Android lifecycle guidance). In a Fragment, use viewLifecycleOwner.lifecycle and viewLifecycleOwner.lifecycleScope so collection ends when the view is destroyed:
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
combine(
etEmail.textChanges().map { Patterns.EMAIL_ADDRESS.matcher(it).matches() },
etPassword.textChanges().map { it.length > 7 },
) { email, password -> email && password }
.collect { btLogin.isEnabled = it }
}
}
Each resumed Flow collection registers a new listener and reads the current value again. Use dropInitialValue() on an InitialValueFlow when only later changes matter.
Existing channel and action overloads
A ReceiveChannel is hot: the binding starts when the channel is created, not when it is consumed. Create it inside the lifecycle block and pass that block's scope so its listener is removed at STOPPED:
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
findViewById<ViewPager2>(R.id.vpSlides)
.pageSelections(this) // ReceiveChannel<Int>
.consumeEach { page -> /* handle the selected page */ }
}
}
The passed scope's Job owns the channel. Cancelling that job or the returned channel removes the listener; cancelling the channel does not cancel the scope. A scope that is already cancelled registers nothing. Flow and channel bindings can be collected or created from any dispatcher, while Android listener registration and cleanup run on the main thread. Synchronous action overloads must be called on the main thread.
For new code, replace view.clicks(scope) with view.clicks() and collect the Flow. Replace actor-based view.clicks(scope) { action() } with a Flow collector:
findViewById<AppCompatButton>(R.id.btConfirm)
.clicks()
.onEach { /* perform the action */ }
.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
.launchIn(lifecycleScope)
Some bindings use Android's single-listener setOn...Listener methods, such as View.clicks(). Give each such callback one active owner: a second binding or direct listener assignment can replace the first listener, and cleanup can clear the replacement. Share events downstream of one binding if several consumers need them.
Channel overloads default to Channel.RENDEZVOUS; Flow producers use a buffered channel. Both default to suspending overflow: when the buffer is full, Corbind queues sends in order without blocking Android callbacks. A slow consumer can therefore accumulate pending sends without bound. Use Channel.CONFLATED for channel bindings, or Flow conflate() / buffer(..., onBufferOverflow = ...) when discarding intermediate events is acceptable. Choose this explicitly for each event source.
More examples are in module descriptions and source code.
Sample app
The sample module is a demonstration app. Its release variant uses the Android debug signing configuration so local and CI builds can produce an installable APK without a release key. The resulting sample-release.apk is for demonstration and build checks only; it is not a production release and should not be distributed as one. A production app needs its own release signing key and release process.
Missed or forgot something?
If I forgot something or you have any ideas what can be added or corrected, please create an issue or contact me directly.
Special thanks to
Jake Wharton. This project is inspired by RxBinding.
License
Copyright 2019-2026 Vladimir Raupov
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
