Commit ec2b536
feat(android): support Activity Result API for native modules (#57798)
Summary:
Rendered readme can be found [here](https://github.com/matinzd/react-native/blob/feat/permission_contracts_android/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/activityresult/__docs__/README.md).
Bare React Native has no way for a native module to use AndroidX `ActivityResultContract`s. Modules are stuck with `ActivityEventListener` and self-assigned int request codes, and some contracts (e.g. Health Connect's permission contract) have no `startActivityForResult` equivalent at all. Calling `registerForActivityResult` on `getCurrentActivity()` instead is a dead end: the lifecycle-observing overload crashes with `LifecycleOwner ... is attempting to register while current state is RESUMED. LifecycleOwners must call register before they are STARTED.`, because AndroidX only allows it before the Activity is `STARTED` — and native modules are created lazily, long after that ([https://github.com/react/react-native/issues/33639](https://github.com/facebook/react-native/issues/33639)).
Libraries work around this by demanding glue code in the consumer's `MainActivity`: react-native-health-connect today requires every app to add `HealthConnectPermissionDelegate.setPermissionDelegate(this)`. The proposed alternative — shipping a transparent `Activity` in the library's manifest ([matinzd/react-native-health-connect#266](matinzd/react-native-health-connect#266), still an unreleased PR) — cuts against Google's single-activity guidance ([https://github.com/react/react-native/issues/33639](https://github.com/facebook/react-native/issues/33639), [https://github.com/react/react-native/issues/36377](https://github.com/facebook/react-native/issues/36377)). Expo solved this with [`registerActivityContracts`](https://docs.expo.dev/modules/module-api/#registeractivitycontracts); bare RN has no equivalent.
`ReactActivity` already extends `ComponentActivity`, so it already owns a real `ActivityResultRegistry` and routes results into it. Core just needs to hand modules a path to that registry:
```kotlin
private val getContent = reactContext.registerForActivityResult(
/* owner = */ this, ActivityResultContracts.GetContent()) { uri -> ... }
getContent.launch("image/*")
```
Design notes:
- API mirrors `ComponentActivity.registerForActivityResult` and returns the real `androidx.activity.result.ActivityResultLauncher<I>`. The one addition is a leading `owner` argument, which scopes the registration key.
- Modules register before an Activity exists (they are created lazily), so the returned launcher binds to the registry on `onHostResume` and queues a `launch()` issued while unbound.
- No changes to `ReactActivity`/`ReactActivityDelegate`/`ReactDelegate`, no new Gradle dependency, no manifest changes, no forked registry. `ActivityEventListener` is untouched.
- Known limitation: on process death, AndroidX redelivers the pending result under the same key, but the module's in-flight state (typically a `Promise`) died with the JS context.
Demos: `SampleTurboModule.requestSamplePermission()` (CAMERA), plus `pickMedia` and `pickMultipleMedia` (photo picker, single and multi select with a JS-controlled limit), surfaced in rn-tester's SampleTurboModule and PhotoPickerAndroid screens.
## Changelog:
[ANDROID] [ADDED] - Add support for Activity Result API for native modules
Pull Request resolved: #57798
Test Plan:
- `./gradlew :packages:react-native:ReactAndroid:compileDebugKotlin` and `:compileDebugJavaWithJavac` pass; codegen emits the sample module methods into `NativeSampleTurboModuleSpec`.
- Flow, ESLint, prettier, and ktfmt clean.
## Example App Recording
https://github.com/user-attachments/assets/63750917-2325-4613-9a0d-b7241ae026eb
Reviewed By: javache
Differential Revision: D115622269
Pulled By: Abbondanzo
fbshipit-source-id: ce623a84d3c5b29f1bae57c2c177517d0cd341901 parent ab1159b commit ec2b536
17 files changed
Lines changed: 1724 additions & 4 deletions
File tree
- packages
- react-native
- ReactAndroid
- api
- src
- main/java/com/facebook/react
- activityresult
- __docs__
- bridge
- test/java/com/facebook/react/activityresult
- ReactCommon/react/nativemodule/samples/platform
- android
- ios/ReactCommon
- rn-tester
- android/app/src/main
- java/com/facebook/react/uiapp
- js
- examples
- PhotoPickerAndroid
- TurboModule
- utils
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1016 | 1016 | | |
1017 | 1017 | | |
1018 | 1018 | | |
| 1019 | + | |
| 1020 | + | |
| 1021 | + | |
| 1022 | + | |
1019 | 1023 | | |
1020 | 1024 | | |
1021 | 1025 | | |
| |||
Lines changed: 128 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
Lines changed: 84 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
0 commit comments