mirror of
https://github.com/react/react-native.git
synced 2026-09-28 13:23:09 +08:00
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](https://github.com/matinzd/react-native-health-connect/pull/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: https://github.com/react/react-native/pull/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: ce623a84d3c5b29f1bae57c2c177517d0cd34190
83 lines
2.6 KiB
JavaScript
83 lines
2.6 KiB
JavaScript
/**
|
|
* 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.
|
|
*
|
|
* @flow strict-local
|
|
* @format
|
|
*/
|
|
|
|
import type {CodegenTypes, RootTag, TurboModule} from 'react-native';
|
|
|
|
import {TurboModuleRegistry} from 'react-native';
|
|
|
|
export enum EnumInt {
|
|
A = 23,
|
|
B = 42,
|
|
}
|
|
|
|
type ObjectStruct = {
|
|
a: number,
|
|
b: string,
|
|
c?: ?string,
|
|
};
|
|
|
|
export interface Spec extends TurboModule {
|
|
readonly onPress: CodegenTypes.EventEmitter<void>;
|
|
readonly onClick: CodegenTypes.EventEmitter<string>;
|
|
readonly onChange: CodegenTypes.EventEmitter<ObjectStruct>;
|
|
readonly onSubmit: CodegenTypes.EventEmitter<ObjectStruct[]>;
|
|
readonly getConstants: () => {
|
|
const1: boolean,
|
|
const2: number,
|
|
const3: string,
|
|
};
|
|
readonly voidFunc: () => void;
|
|
readonly getBool: (arg: boolean) => boolean;
|
|
readonly getEnum?: (arg: EnumInt) => EnumInt;
|
|
readonly getNumber: (arg: number) => number;
|
|
readonly getString: (arg: string) => string;
|
|
readonly getArray: (arg: Array<unknown>) => Array<unknown>;
|
|
readonly getObject: (
|
|
arg: CodegenTypes.UnsafeObject,
|
|
) => CodegenTypes.UnsafeObject;
|
|
readonly getUnsafeObject: (
|
|
arg: CodegenTypes.UnsafeObject,
|
|
) => CodegenTypes.UnsafeObject;
|
|
readonly getRootTag: (arg: RootTag) => RootTag;
|
|
readonly getValue: (
|
|
x: number,
|
|
y: string,
|
|
z: CodegenTypes.UnsafeObject,
|
|
) => CodegenTypes.UnsafeObject;
|
|
readonly getArrayBuffer: (buffer: ArrayBuffer) => ArrayBuffer;
|
|
readonly createNativeBuffer: (size: number) => ArrayBuffer;
|
|
readonly processAsyncBuffer: (payload: ArrayBuffer) => Promise<number>;
|
|
readonly getValueWithCallback: (callback: (value: string) => void) => void;
|
|
readonly getValueWithPromise: (error: boolean) => Promise<string>;
|
|
readonly voidFuncThrows?: () => void;
|
|
readonly getObjectThrows?: (
|
|
arg: CodegenTypes.UnsafeObject,
|
|
) => CodegenTypes.UnsafeObject;
|
|
readonly promiseThrows?: () => Promise<void>;
|
|
readonly voidFuncAssert?: () => void;
|
|
readonly getObjectAssert?: (
|
|
arg: CodegenTypes.UnsafeObject,
|
|
) => CodegenTypes.UnsafeObject;
|
|
readonly promiseAssert?: () => Promise<void>;
|
|
readonly getImageUrl?: () => Promise<string | null>;
|
|
readonly requestSamplePermission?: () => Promise<boolean>;
|
|
readonly pickMedia?: (mimeType: ?string) => Promise<string | null>;
|
|
readonly pickMultipleMedia?: (
|
|
mimeType: ?string,
|
|
maxItems: number,
|
|
) => Promise<Array<string>>;
|
|
readonly startSecondActivity?: () => void;
|
|
}
|
|
|
|
const RNTesterSampleTurboModule: Spec =
|
|
TurboModuleRegistry.getEnforcing<Spec>('SampleTurboModule');
|
|
|
|
export default RNTesterSampleTurboModule;
|