From 8f0f204eded0b9e0c1279e68136c5cc25b445c2e Mon Sep 17 00:00:00 2001 From: Peter Abbondanzo Date: Fri, 25 Sep 2026 11:55:44 -0700 Subject: [PATCH] Fix Activity Result launcher compilation 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?) {