Description
Flow Typing NativeModules
The TurboModule system is nearly feature-complete on both Android and iOS. Before we rollout the system, we'd like to migrate all our OSS NativeModules to use it. As a prerequisite for this migration, all the the NativeModules must have spec files. There are currently 41 NativeModules in total, so there is a bunch of work to do. We'd love to use this as an opportunity to help get more people involved by contributing to React Native.
Instructions
- Pick an unclaimed module and comment that you're working on it.
- Create a Spec file called
NativeXYZ.js
, whereXYZ
is the name of the NativeModule. - Define flow types based on the NativeModule's native methods.
- Change call-sites from
NativeModules.XYZ
toNativeXYZ
- Make sure flow passes
- Do one module per PR
What is a Spec file?
A Spec file looks like this:
NativeAnalytics.js
/**
* Copyright 2004-present Facebook. All Rights Reserved.
*
* @flow strict-local
* @format
*/
'use strict';
import type {TurboModule} from 'RCTExport';
import * as TurboModuleRegistry from 'TurboModuleRegistry';
export interface Spec extends TurboModule {
+getConstants: () => {|
constant1: string,
constant2: boolean,
|};
+logCounter: (key: string, value: number) => void;
+logEvent: (eventName: string, data: Object, analyticsModule: ?string) => void;
+logRealtimeEvent: (
eventName: string,
data: Object,
analyticsModule: ?string,
) => void;
}
export default TurboModuleRegistry.getEnforcing<Spec>(
'Analytics',
);
Observations
- We use
TurboModuleRegistry.getEnforcing<Spec>('Analytics')
as opposed toNativeModules.Analytics
to require the NativeModule.TurboModuleRegistry.getEnforcing
is an indirection that allows us to require both NativeModules and TurboModules. It throws if the NativeModule isn't there. In the case that a NativeModule is platform-specific, please use Platform.OS and conditionally callTurboModuleRegistry.getEnforcing
. - Each Spec file exports an
interface
calledSpec
that extendsTurboModule
. This interface contains typings for methods exposed by the NativeModule/TurboModule. - NativeModule constants are declared in the return type of the
getConstants()
method. The old way of accessing constants as properties on NativeModule objects is deprecated and unsupported by the TurboModule system. On iOS, these constants map to the return type ofconstantsToExport
in iOS andgetConstants
in Android. - Each Spec file is named
Native*.js
. The filename matters because our codegen uses it to name the generated Java interfaces, ObjC protocols, and C++ TurboModule subclasses.NativeAnalytics.js
, for instance, will generate a Java interface and ObjC protocol calledNativeAnalyticsSpec
, and a C++ TurboModule class calledNativeAnalyticsSpecJSI
. After these Spec files are typed, we'll generate and check in the codegen output into this repository. Then, after we open source our codegen, we'll delete the generated interfaces, protocols, and C++ classes from this repository.- Note that the naming convention used here is not finalized, but stable enough for the time being.
- This isn't a ViewManager. We're currently not focusing on ViewManagers. Therefore, there's no need to write spec files for them.
- We're not using imported types. Our codegen doesn't support imported types. So, for the time being, all types used by the spec must be declared in the same file.
Supported Types
- Methods with simple args:
string
boolean
number
Object
Array<*>*
Function
and typed function:- Methods with nullable args
?string
?Object
?Array<*>*
?Function
and nullable typed functionnumber
andboolean
are not supported
- Methods with inline complex Object typed args
- e.g.
(x: {|foo: string, ... |}) => void
- e.g.
- Methods that return a
Promise<*>*
- Synchronous methods that return simple non-nullable types
string
number
boolean
Object
Array<*>*
- Optional methods with all variants above
- Typed exported constants
- Note: Flow unions aren't supported
How do you type NativeModules?
You have to deduce the types by looking at the NativeModule implementations on iOS and Android. Some NativeModules already have JS wrappers, so you could also look at those for writing the Spec files.
Guidelines
-
How do you know which methods are exported to JS?
- On Android: Look for NativeModule non-inherited class methods annotated with
@ReactMethod
. - On iOS: Look for NativeModule non-inherited and inherited class methods wrapped in
RCT_EXPORT_*_METHOD
macros .
- On Android: Look for NativeModule non-inherited class methods annotated with
-
How do you know when a method should have a
Promise
return type?- On Android: The last argument of the method is the
Promise
object. - On iOS: The last two arguments are a
RCTPromiseResolveBlock
andRCTPromiseRejectBlock
- On Android: The last argument of the method is the
-
What should the JS method name be?
- On Android: It's the name of the Java method.
- On iOS: It's the part of the selector before the first argument/color.
-
NSDictionary
on iOS, andReadableMap
andWritableMap
on Android translate to object literals in JS. -
RCTResponseSenderBlock
andRCTResponseErrorBlock
on iOS, andCallback
on Android translate to function literals in JS. -
Try to avoid using
Function
andObject
whenever possible. -
In the case that a method or constant is unavailable on one platform, make it optional.
-
In the case that a NativeModule is unavailable on one platform, make it optional.
-
Where do you put the Spec file?
- Most NativeModules belong to a Library (see the JS section of the table below), so you could write the Spec in the library. For the other NativeModules, write the spec inside
react-native-github/Libraries/NativeModules/specs
.
- Most NativeModules belong to a Library (see the JS section of the table below), so you could write the Spec in the library. For the other NativeModules, write the spec inside
Platform-specific NativeModules
For platform-specific NativeModules, use Platform.OS
to conditionally call TurboModuleRegistry.getEnforcing<Spec>
. When changing the call-sites of these NativeModules, you may have to guard them with null checks to please flow.
Call-site Before
const NativeModules = require('NativeModules');
const IntentAndroid = NativeModules.IntentAndroid;
function foo() {
if (Platform.OS === 'android') {
IntentAndroid.method();
}
}
Call-site After
const NativeIntentAndroid = require('NativeIntentAndroid');
const {Platform} = require('react-native');
function foo() {
if (Platform.OS === 'android') {
if (NativeIntentAndroid != null) {
NativeIntentAndroid.method();
}
}
}
NativeModule List
Please claim a NativeModule from the list below.
⬜️ - Unclaimed
🚧 - Claimed / PR in progress
✅ - PR Merged
Status | JS Name | JS NativeModule Wrappers | iOS | Android |
---|---|---|---|---|
✅ | AccessibilityInfo | react-native-github/Libraries/Components/AccessibilityInfo/AccessibilityInfo.android.js | AccessibilityInfoModule.java | |
✅ | AccessibilityManager | react-native-github/Libraries/Components/AccessibilityInfo/AccessibilityInfo.ios.js | RCTAccessibilityManager.m | |
✅ | AlertManager | react-native-github/Libraries/Alert/Alert.js | RCTAlertManager.m | |
✅ | AnimationDebugModule | AnimationsDebugModule.java | ||
✅ | AppState | react-native-github/Libraries/AppState/AppState.js | RCTAppState.m | AppStateModule.java |
✅ | BlobModule | react-native-github/Libraries/Blob/BlobManager.js | RCTBlobManager.mm | BlobModule.java |
✅ | DatePickerAndroid | react-native-github/Libraries/Components/DatePickerAndroid/DatePickerAndroid.android.js | DatePickerDialogModule.java | |
✅ | DevLoadingView | react-native-github/Libraries/Utilities/HMRLoadingView.ios.js | RCTDevLoadingView.m | |
✅ | DevSettings | N/A | RCTDevSettings.mm | DevSettingsModule.java |
✅ | DeviceEventManager | N/A | DeviceEventManagerModule.java | |
✅ | DeviceInfo | react-native-github/Libraries/Utilities/DeviceInfo.js | RCTDeviceInfo.m | DeviceInfoModule.java |
✅ | DialogManagerAndroid | N/A | DialogModule.java | |
✅ | ExceptionsManager | react-native-github/Libraries/Core/ExceptionsManager.js | RCTExceptionsManager.m | ExceptionsManagerModule.java |
✅ | FileReaderModule | N/A | RCTFileReaderModule.m | FileReaderModule.java |
✅ | HeadlessJsTaskSupport | N/A | HeadlessJsTaskSupportModule.java | |
✅ | I18nManager | react-native-github/Libraries/ReactNative/I18nManager.js | RCTI18nManager.m | I18nManagerModule.java |
✅ | ImageEditor | N/A | RCTImageEditingManager.m | ImageEditingModule.java |
✅ | ImageStore | N/A | RCTImageStoreManager.m | ImageStoreManager.java |
✅ | IntentAndroid | react-native-github/Libraries/Linking/Linking.js | IntentModule.java | |
✅ | JSCHeapCapture | react-native-github/Libraries/Utilities/HeapCapture.js | JSCHeapCapture.java | |
✅ | JSCSamplingProfiler | react-native-github/Libraries/Performance/SamplingProfiler.js | JSCSamplingProfiler.java | |
✅ | JSDevSupport | react-native-github/Libraries/Utilities/JSDevSupportModule.js | JSDevSupport.java | |
✅ | KeyboardObserver | react-native-github/Libraries/Components/Keyboard/Keyboard.js (NativeEventEmitter) | RCTKeyboardObserver.m | |
✅ | LinkingManager | react-native-github/Libraries/Linking/Linking.js | RCTLinkingManager.m | |
✅ | ModalManager | N/A | RCTModalManager.m | |
✅ | NativeAnimatedModule | react-native-github/Libraries/Animated/src/NativeAnimatedHelper.js | RCTNativeAnimatedModule.m | NativeAnimatedModule.java |
✅ | Networking | react-native-github/Libraries/Network/RCTNetworking.android.js | RCTNetworking.mm | NetworkingModule.java |
✅ | react-native-github/Libraries/Network/RCTNetworking.ios.js | |||
✅ | PermissionsAndroid | react-native-github/Libraries/PermissionsAndroid/PermissionsAndroid.js | PermissionsModule.java | |
✅ | PlatformConstants | react-native-github/Libraries/Utilities/Platform.android.js | RCTPlatform.m | AndroidInfoModule.java |
✅ | react-native-github/Libraries/Utilities/Platform.ios.js | |||
✅ | RedBox | N/A | RCTRedBox.m | |
✅ | SettingsManager | react-native-github/Libraries/Settings/Settings.ios.js | RCTSettingsManager | |
✅ | SourceCode | react-native-github/Libraries/Share/Share.js | RCTSourceCode.m | SourceCodeModule.java |
✅ | TVNavigationEventEmitter | react-native-github/Libraries/Components/AppleTV/TVEventHandler.js | RCTTVNavigationEventEmitter.m | |
✅ | TimePickerAndroid | react-native-github/Libraries/Components/TimePickerAndroid/TimePickerAndroid.android.js | TimePickerDialogModule.java | |
✅ | react-native-github/Libraries/Components/TimePickerAndroid/TimePickerAndroid.ios.js | |||
✅ | Timing | react-native-github/Libraries/Core/Timers/JSTimers.js | RCTTiming.m | Timing.java |
✅ | ToastAndroid | react-native-github/Libraries/Components/ToastAndroid/ToastAndroid.android.js | ToastModule.java | |
✅ | UIManager | RCTUIManager.m | UIManagerModule.java | |
✅ | WebSocketModule | react-native-github/Libraries/WebSocket/WebSocket.js | RCTWebSocketModule.m | WebSocketModule.java |