Skip to content

👻 Flow Typing NativeModules #24875

Closed
Closed
@RSNara

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

  1. Pick an unclaimed module and comment that you're working on it.
  2. Create a Spec file called NativeXYZ.js, where XYZ is the name of the NativeModule.
  3. Define flow types based on the NativeModule's native methods.
  4. Change call-sites from NativeModules.XYZ to NativeXYZ
  5. Make sure flow passes
  6. 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 to NativeModules.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 call TurboModuleRegistry.getEnforcing.
  • Each Spec file exports an interface called Spec that extends TurboModule. 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 of constantsToExport in iOS and getConstants 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 called NativeAnalyticsSpec, and a C++ TurboModule class called NativeAnalyticsSpecJSI. 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 function
    • number and boolean are not supported
  • Methods with inline complex Object typed args
    • e.g. (x: {|foo: string, ... |}) => void
  • 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 .
  • 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 and RCTPromiseRejectBlock
  • 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, and ReadableMap and WritableMap on Android translate to object literals in JS.

  • RCTResponseSenderBlock and RCTResponseErrorBlock on iOS, and Callback on Android translate to function literals in JS.

  • Try to avoid using Function and Object 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.

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

Metadata

Assignees

Labels

FlowGood first issueInterested in collaborating? Take a stab at fixing one of these issues.Help Wanted :octocat:Issues ideal for external contributors.Native ModuleResolution: LockedThis issue was locked by the bot.Type: DiscussionLong running discussion.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions