From 844a55a39f91e467d033453c4b9c767bf287f380 Mon Sep 17 00:00:00 2001 From: Peter Abbondanzo Date: Fri, 25 Sep 2026 12:08:44 -0700 Subject: [PATCH 1/2] Fix Activity Result launcher compilation (#58688) Summary: Different AndroidX artifacts expose `ActivityResultLauncher.contract` as either a Kotlin property or a Java `getContract()` method. A Kotlin subclass cannot override both source representations even though they have the same JVM signature. Route the contract accessor through a package-private Java superclass so both representations resolve to the same JVM method. Use the bridge in production and test launchers, verify that the registered contract is preserved, and allowlist this required Java compatibility source. Changelog: [Android][Fixed] - Fix Activity Result launcher compilation across AndroidX source variants Differential Revision: D121845437 --- .../ActivityResultLauncherCompat.java | 24 +++++++++++++++++++ .../DeferredActivityResultLauncher.kt | 4 ++-- .../ReactActivityResultCallerImplTest.kt | 8 +++++++ .../ReactActivityResultCallerThreadingTest.kt | 4 +--- 4 files changed, 35 insertions(+), 5 deletions(-) create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/activityresult/ActivityResultLauncherCompat.java diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/activityresult/ActivityResultLauncherCompat.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/activityresult/ActivityResultLauncherCompat.java new file mode 100644 index 00000000000..a326c035b92 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/activityresult/ActivityResultLauncherCompat.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.activityresult; + +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContract; + +abstract class ActivityResultLauncherCompat extends ActivityResultLauncher { + private final ActivityResultContract contract; + + ActivityResultLauncherCompat(ActivityResultContract contract) { + this.contract = contract; + } + + @Override + public ActivityResultContract getContract() { + return contract; + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/activityresult/DeferredActivityResultLauncher.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/activityresult/DeferredActivityResultLauncher.kt index 7736faf6a39..7e62a3b46f7 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/activityresult/DeferredActivityResultLauncher.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/activityresult/DeferredActivityResultLauncher.kt @@ -26,10 +26,10 @@ import com.facebook.react.common.ReactConstants */ internal class DeferredActivityResultLauncher( private val key: String, - override val contract: ActivityResultContract, + contract: ActivityResultContract, private val onUnregister: () -> Unit, private val onLaunchFailure: (RuntimeException) -> Unit = {}, -) : ActivityResultLauncher() { +) : ActivityResultLauncherCompat(contract) { private class PendingLaunch(val input: I, val options: ActivityOptionsCompat?) diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/activityresult/ReactActivityResultCallerImplTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/activityresult/ReactActivityResultCallerImplTest.kt index 5e601ebc2fa..b7af342fa58 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/activityresult/ReactActivityResultCallerImplTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/activityresult/ReactActivityResultCallerImplTest.kt @@ -81,6 +81,14 @@ class ReactActivityResultCallerImplTest { caller = ReactActivityResultCallerImpl(reactContext) } + @Test + fun launcherExposesRegisteredContract() { + val contract = GetContent() + val launcher = caller.registerForActivityResult(moduleA, contract) {} + + assertThat(launcher.contract).isSameAs(contract) + } + @Test fun twoOwnersMayRegisterTheSameStockContract() { caller.registerForActivityResult(moduleA, GetContent()) {} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/activityresult/ReactActivityResultCallerThreadingTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/activityresult/ReactActivityResultCallerThreadingTest.kt index 78e58d6d3ce..8d73eb753cd 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/activityresult/ReactActivityResultCallerThreadingTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/activityresult/ReactActivityResultCallerThreadingTest.kt @@ -11,7 +11,6 @@ import android.app.Activity import android.content.ActivityNotFoundException import android.os.Bundle import android.os.Looper -import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultRegistry import androidx.activity.result.ActivityResultRegistryOwner import androidx.activity.result.contract.ActivityResultContract @@ -65,8 +64,7 @@ class ReactActivityResultCallerThreadingTest { .orEmpty() } - private class ThrowingLauncher : ActivityResultLauncher() { - override val contract: ActivityResultContract = GetContent() + private class ThrowingLauncher : ActivityResultLauncherCompat(GetContent()) { var launchCount = 0 override fun launch(input: String, options: ActivityOptionsCompat?) { From 8bd899663d2a860130842bf1a0e1c8ad91ead007 Mon Sep 17 00:00:00 2001 From: Peter Abbondanzo Date: Fri, 25 Sep 2026 12:08:44 -0700 Subject: [PATCH 2/2] Upgrade AndroidX Activity to 1.10.1 Summary: ReactAndroid exposes AndroidX Activity Result types in its public API but currently receives `androidx.activity:activity:1.7.0` only transitively through AppCompat. Declare Activity 1.10.1 as a direct API dependency. This aligns OSS builds with the version used internally and lets the Kotlin `contract` property compile directly, so the compatibility bridge from the parent diff can be removed. Changelog: [Android][Changed] - Upgrade AndroidX Activity to 1.10.1 Differential Revision: D121847691 --- .../ReactAndroid/build.gradle.kts | 1 + .../ActivityResultLauncherCompat.java | 24 ------------------- .../DeferredActivityResultLauncher.kt | 4 ++-- .../ReactActivityResultCallerImplTest.kt | 8 ------- .../ReactActivityResultCallerThreadingTest.kt | 4 +++- .../react-native/gradle/libs.versions.toml | 2 ++ 6 files changed, 8 insertions(+), 35 deletions(-) delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/activityresult/ActivityResultLauncherCompat.java diff --git a/packages/react-native/ReactAndroid/build.gradle.kts b/packages/react-native/ReactAndroid/build.gradle.kts index 16e2101973f..2b31c54fe0f 100644 --- a/packages/react-native/ReactAndroid/build.gradle.kts +++ b/packages/react-native/ReactAndroid/build.gradle.kts @@ -729,6 +729,7 @@ tasks.withType().configureEach { } dependencies { + api(libs.androidx.activity) api(libs.androidx.appcompat) api(libs.androidx.appcompat.resources) api(libs.androidx.autofill) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/activityresult/ActivityResultLauncherCompat.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/activityresult/ActivityResultLauncherCompat.java deleted file mode 100644 index a326c035b92..00000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/activityresult/ActivityResultLauncherCompat.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.react.activityresult; - -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContract; - -abstract class ActivityResultLauncherCompat extends ActivityResultLauncher { - private final ActivityResultContract contract; - - ActivityResultLauncherCompat(ActivityResultContract contract) { - this.contract = contract; - } - - @Override - public ActivityResultContract getContract() { - return contract; - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/activityresult/DeferredActivityResultLauncher.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/activityresult/DeferredActivityResultLauncher.kt index 7e62a3b46f7..7736faf6a39 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/activityresult/DeferredActivityResultLauncher.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/activityresult/DeferredActivityResultLauncher.kt @@ -26,10 +26,10 @@ import com.facebook.react.common.ReactConstants */ internal class DeferredActivityResultLauncher( private val key: String, - contract: ActivityResultContract, + override val contract: ActivityResultContract, private val onUnregister: () -> Unit, private val onLaunchFailure: (RuntimeException) -> Unit = {}, -) : ActivityResultLauncherCompat(contract) { +) : ActivityResultLauncher() { private class PendingLaunch(val input: I, val options: ActivityOptionsCompat?) diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/activityresult/ReactActivityResultCallerImplTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/activityresult/ReactActivityResultCallerImplTest.kt index b7af342fa58..5e601ebc2fa 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/activityresult/ReactActivityResultCallerImplTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/activityresult/ReactActivityResultCallerImplTest.kt @@ -81,14 +81,6 @@ class ReactActivityResultCallerImplTest { caller = ReactActivityResultCallerImpl(reactContext) } - @Test - fun launcherExposesRegisteredContract() { - val contract = GetContent() - val launcher = caller.registerForActivityResult(moduleA, contract) {} - - assertThat(launcher.contract).isSameAs(contract) - } - @Test fun twoOwnersMayRegisterTheSameStockContract() { caller.registerForActivityResult(moduleA, GetContent()) {} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/activityresult/ReactActivityResultCallerThreadingTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/activityresult/ReactActivityResultCallerThreadingTest.kt index 8d73eb753cd..78e58d6d3ce 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/activityresult/ReactActivityResultCallerThreadingTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/activityresult/ReactActivityResultCallerThreadingTest.kt @@ -11,6 +11,7 @@ import android.app.Activity import android.content.ActivityNotFoundException import android.os.Bundle import android.os.Looper +import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultRegistry import androidx.activity.result.ActivityResultRegistryOwner import androidx.activity.result.contract.ActivityResultContract @@ -64,7 +65,8 @@ class ReactActivityResultCallerThreadingTest { .orEmpty() } - private class ThrowingLauncher : ActivityResultLauncherCompat(GetContent()) { + private class ThrowingLauncher : ActivityResultLauncher() { + override val contract: ActivityResultContract = GetContent() var launchCount = 0 override fun launch(input: String, options: ActivityOptionsCompat?) { diff --git a/packages/react-native/gradle/libs.versions.toml b/packages/react-native/gradle/libs.versions.toml index 31515dcac88..f6cafe2da1a 100644 --- a/packages/react-native/gradle/libs.versions.toml +++ b/packages/react-native/gradle/libs.versions.toml @@ -7,6 +7,7 @@ buildTools = "37.0.0" ndkVersion = "27.1.12297006" # Dependencies versions agp = "9.2.1" +androidx-activity = "1.10.1" androidx-annotation = "1.6.0" androidx-appcompat = "1.7.0" androidx-autofill = "1.3.0" @@ -52,6 +53,7 @@ gflags="2.2.0" nlohmannjson="3.11.2" [libraries] +androidx-activity = { module = "androidx.activity:activity", version.ref = "androidx-activity" } androidx-annotation = { module = "androidx.annotation:annotation", version.ref = "androidx-annotation" } androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" } androidx-appcompat-resources = { module = "androidx.appcompat:appcompat-resources", version.ref = "androidx-appcompat" }