diff --git a/README.md b/README.md index 2b1bcb24..674985bc 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ implementation "io.github.reactivecircus.flowbinding:flowbinding-android:${flowb ### AndroidX Bindings ```groovy +implementation "io.github.reactivecircus.flowbinding:flowbinding-activity:${flowbinding_version}" 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}" @@ -147,6 +148,7 @@ Our goal is to provide most of the bindings provided by **RxBinding**, while shi List of all bindings available: * [Platform bindings][flowbinding-android] +* [AndroidX Activity bindings][flowbinding-activity] * [AndroidX AppCompat bindings][flowbinding-appcompat] * [AndroidX Core bindings][flowbinding-core] * [AndroidX DrawerLayout bindings][flowbinding-drawerlayout] @@ -192,6 +194,7 @@ limitations under the License. [flow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/ [kotlinx-coroutines]: https://github.com/Kotlin/kotlinx.coroutines [flowbinding-android]: flowbinding-android/ +[flowbinding-activity]: flowbinding-activity/ [flowbinding-appcompat]: flowbinding-appcompat/ [flowbinding-core]: flowbinding-core/ [flowbinding-drawerlayout]: flowbinding-drawerlayout/ diff --git a/flowbinding-activity/README.md b/flowbinding-activity/README.md new file mode 100644 index 00000000..97fe061e --- /dev/null +++ b/flowbinding-activity/README.md @@ -0,0 +1,19 @@ +# FlowBinding Activity + +This module provides bindings for the **AndroidX Activity** library. + +## Transitive Dependency + +`androidx.activity:activity-ktx` + +## Download + +```groovy +implementation "io.github.reactivecircus.flowbinding:flowbinding-activity:${flowbinding_version}" +``` + +## Available Bindings + +```kotlin +fun OnBackPressedDispatcher.backPresses(enabled: Boolean): Flow +``` diff --git a/flowbinding-activity/build.gradle b/flowbinding-activity/build.gradle new file mode 100644 index 00000000..dff303a0 --- /dev/null +++ b/flowbinding-activity/build.gradle @@ -0,0 +1,26 @@ +plugins { + id 'flowbinding-plugin' + id 'com.android.library' + id 'kotlin-android' + id 'com.vanniktech.maven.publish' + id 'io.github.reactivecircus.firestorm' +} + +android { + defaultConfig { + testApplicationId 'reactivecircus.flowbinding.activity.test' + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } +} + +dependencies { + implementation project(':flowbinding-common') + + implementation "androidx.activity:activity-ktx:${versions.androidx.activity}" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:${versions.kotlinx.coroutines}" + + lintChecks project(":lint-rules") + + androidTestImplementation project(':testing-infra') + androidTestImplementation project(':flowbinding-activity:fixtures') +} diff --git a/flowbinding-activity/fixtures/build.gradle b/flowbinding-activity/fixtures/build.gradle new file mode 100644 index 00000000..32827435 --- /dev/null +++ b/flowbinding-activity/fixtures/build.gradle @@ -0,0 +1,13 @@ +plugins { + id 'flowbinding-plugin' + id 'com.android.library' + id 'kotlin-android' +} + +android.buildFeatures.viewBinding = true + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${versions.kotlin}" + implementation "androidx.activity:activity-ktx:${versions.androidx.activity}" + implementation "androidx.fragment:fragment:${versions.androidx.fragment}" +} diff --git a/flowbinding-activity/fixtures/src/main/AndroidManifest.xml b/flowbinding-activity/fixtures/src/main/AndroidManifest.xml new file mode 100644 index 00000000..e9ddc940 --- /dev/null +++ b/flowbinding-activity/fixtures/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/flowbinding-activity/fixtures/src/main/java/reactivecircus/flowbinding/activity/fixtures/ActivityFragment.kt b/flowbinding-activity/fixtures/src/main/java/reactivecircus/flowbinding/activity/fixtures/ActivityFragment.kt new file mode 100644 index 00000000..71d98a3b --- /dev/null +++ b/flowbinding-activity/fixtures/src/main/java/reactivecircus/flowbinding/activity/fixtures/ActivityFragment.kt @@ -0,0 +1,5 @@ +package reactivecircus.flowbinding.activity.fixtures + +import androidx.fragment.app.Fragment + +class ActivityFragment : Fragment(R.layout.fragment_activity) diff --git a/flowbinding-activity/fixtures/src/main/res/layout/fragment_activity.xml b/flowbinding-activity/fixtures/src/main/res/layout/fragment_activity.xml new file mode 100644 index 00000000..03da6d67 --- /dev/null +++ b/flowbinding-activity/fixtures/src/main/res/layout/fragment_activity.xml @@ -0,0 +1,4 @@ + + diff --git a/flowbinding-activity/gradle.properties b/flowbinding-activity/gradle.properties new file mode 100644 index 00000000..1a24540c --- /dev/null +++ b/flowbinding-activity/gradle.properties @@ -0,0 +1,4 @@ +POM_ARTIFACT_ID=flowbinding-activity +POM_NAME=FlowBinding Activity +POM_DESCRIPTION=Kotlin Flow binding APIs for AndroidX Activity +POM_PACKAGING=aar diff --git a/flowbinding-activity/src/androidTest/java/reactivecircus/flowbinding/activity/OnBackPressedDispatcherBackPressedFlowTest.kt b/flowbinding-activity/src/androidTest/java/reactivecircus/flowbinding/activity/OnBackPressedDispatcherBackPressedFlowTest.kt new file mode 100644 index 00000000..09a5e39d --- /dev/null +++ b/flowbinding-activity/src/androidTest/java/reactivecircus/flowbinding/activity/OnBackPressedDispatcherBackPressedFlowTest.kt @@ -0,0 +1,31 @@ +package reactivecircus.flowbinding.activity + +import androidx.test.filters.LargeTest +import org.amshove.kluent.shouldEqual +import org.junit.Test +import reactivecircus.blueprint.testing.action.pressBack +import reactivecircus.flowbinding.activity.fixtures.ActivityFragment +import reactivecircus.flowbinding.testing.FlowRecorder +import reactivecircus.flowbinding.testing.launchTest +import reactivecircus.flowbinding.testing.recordWith + +@LargeTest +class OnBackPressedDispatcherBackPressedFlowTest { + + @Test + fun onBackPressedDispatcherBackPresses() { + launchTest { + val recorder = FlowRecorder(testScope) + fragment.requireActivity().onBackPressedDispatcher.backPresses(owner = fragment).recordWith(recorder) + + pressBack() + recorder.takeValue() shouldEqual Unit + recorder.assertNoMoreValues() + + cancelTestScope() + + pressBack() + recorder.assertNoMoreValues() + } + } +} diff --git a/flowbinding-activity/src/main/AndroidManifest.xml b/flowbinding-activity/src/main/AndroidManifest.xml new file mode 100644 index 00000000..80a3e471 --- /dev/null +++ b/flowbinding-activity/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/flowbinding-activity/src/main/java/reactivecircus/flowbinding/activity/OnBackPressedDispatcherBackPressedFlow.kt b/flowbinding-activity/src/main/java/reactivecircus/flowbinding/activity/OnBackPressedDispatcherBackPressedFlow.kt new file mode 100644 index 00000000..fc7392c8 --- /dev/null +++ b/flowbinding-activity/src/main/java/reactivecircus/flowbinding/activity/OnBackPressedDispatcherBackPressedFlow.kt @@ -0,0 +1,45 @@ +package reactivecircus.flowbinding.activity + +import androidx.activity.OnBackPressedCallback +import androidx.activity.OnBackPressedDispatcher +import androidx.annotation.CheckResult +import androidx.lifecycle.LifecycleOwner +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.conflate +import reactivecircus.flowbinding.common.checkMainThread +import reactivecircus.flowbinding.common.safeOffer + +/** + * Create a [Flow] of on back pressed events on the [OnBackPressedDispatcher] instance. + * + * @param owner the LifecycleOwner which controls when the callback should be invoked. + * + * Note: Created flow keeps a strong reference to the [OnBackPressedDispatcher] instance + * until the coroutine that launched the flow collector is cancelled. + * + * Example of usage: + * + * ``` + * onBackPressedDispatcher.backPresses(lifecycleOwner) + * .onEach { + * // handle back pressed + * } + * .launchIn(uiScope) + * ``` + */ +@CheckResult +@UseExperimental(ExperimentalCoroutinesApi::class) +fun OnBackPressedDispatcher.backPresses(owner: LifecycleOwner): Flow = + callbackFlow { + checkMainThread() + val callback = object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + safeOffer(Unit) + } + } + addCallback(owner, callback) + awaitClose { callback.remove() } + }.conflate() diff --git a/settings.gradle.kts b/settings.gradle.kts index ea7a6914..e71fc362 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -5,6 +5,9 @@ include(":flowbinding-common") include(":flowbinding-android") includeProject(":flowbinding-android:fixtures", "flowbinding-android/fixtures") +include(":flowbinding-activity") +includeProject(":flowbinding-activity:fixtures", "flowbinding-activity/fixtures") + include(":flowbinding-appcompat") includeProject(":flowbinding-appcompat:fixtures", "flowbinding-appcompat/fixtures")