Skip to main content
Join us for React Conf on Oct 7-8. Learn more.
This is unreleased documentation for React Native Next version.
For up-to-date documentation, see the latest version (0.81).
Version: Next

Native Modules

Your React Native application code may need to interact with native platform APIs that aren't provided by React Native or an existing library. You can write the integration code yourself using a Turbo Native Module. This guide will show you how to write one.

The basic steps are:

  1. define a typed JavaScript specification using one of the most popular JavaScript type annotation languages: Flow or TypeScript;
  2. configure your dependency management system to run Codegen, which converts the specification into native language interfaces;
  3. write your application code using your specification; and
  4. write your native platform code using the generated interfaces to write and hook your native code into the React Native runtime environment.

Lets work through each of these steps by building an example Turbo Native Module. The rest of this guide assume that you have created your application running the command:

shell
npx @react-native-community/cli@latest init TurboModuleExample --version latest

Native Persistent Storage

This guide will show you how to write an implementation of the Web Storage API: localStorage. The API is relatable to a React developer who might be writing application code on your project.

To make this work on mobile, we need to use Android and iOS APIs:

1. Declare Typed Specification

React Native provides a tool called Codegen, which takes a specification written in TypeScript or Flow and generates platform specific code for Android and iOS. The specification declares the methods and data types that will pass back and forth between your native code and the React Native JavaScript runtime. A Turbo Native Module is both your specification, the native code you write, and the Codegen interfaces generated from your specification.

To create a specs file:

  1. Inside the root folder of your app, create a new folder called specs.
  2. Create a new file called NativeLocalStorage.ts.
info

You can see all of the types you can use in your specification and the native types that are generated in the Appendix documentation.

Here is an implementation of the localStorage specification:

  • TypeScript
  • Flow
specs/NativeLocalStorage.ts
importtype{TurboModule}from'react-native';
import{TurboModuleRegistry}from'react-native';

exportinterfaceSpecextendsTurboModule{
setItem(value:string, key:string):void;
getItem(key:string):string|null;
removeItem(key:string):void;
clear():void;
}

exportdefault TurboModuleRegistry.getEnforcing<Spec>(
'NativeLocalStorage',
);

2. Configure Codegen to run

The specification is used by the React Native Codegen tools to generate platform specific interfaces and boilerplate for us. To do this, Codegen needs to know where to find our specification and what to do with it. Update your package.json to include:

package.json
"start":"react-native start",
"test":"jest"
},
"codegenConfig":{
"name":"NativeLocalStorageSpec",
"type":"modules",
"jsSrcsDir":"specs",
"android":{
"javaPackageName":"com.nativelocalstorage"
}
},
"dependencies":{

With everything wired up for Codegen, we need to prepare our native code to hook into our generated code.

  • Android
  • iOS

Codegen is executed through the generateCodegenArtifactsFromSchema Gradle task:

bash
cd android
./gradlew generateCodegenArtifactsFromSchema

BUILD SUCCESSFUL in 837ms
14 actionable tasks: 3 executed, 11 up-to-date

This is automatically run when you build your Android application.

3. Write Application Code using the Turbo Native Module

Using NativeLocalStorage, here’s a modified App.tsx that includes some text we want persisted, an input field and some buttons to update this value.

The TurboModuleRegistry supports 2 modes of retrieving a Turbo Native Module:

  • get<T>(name: string): T | null which will return null if the Turbo Native Module is unavailable.
  • getEnforcing<T>(name: string): T which will throw an exception if the Turbo Native Module is unavailable. This assumes the module is always available.
App.tsx
importReactfrom'react';
import{
SafeAreaView,
StyleSheet,
Text,
TextInput,
Button,
}from'react-native';

importNativeLocalStoragefrom'./specs/NativeLocalStorage';

constEMPTY='<empty>';

functionApp():React.JSX.Element{
const[value, setValue]=React.useState<string|null>(null);

const[editingValue, setEditingValue]=React.useState<
string|null
>(null);

React.useEffect(()=>{
const storedValue =NativeLocalStorage?.getItem('myKey');
setValue(storedValue ??'');
},[]);

functionsaveValue(){
NativeLocalStorage?.setItem(editingValue ??EMPTY,'myKey');
setValue(editingValue);
}

functionclearAll(){
NativeLocalStorage?.clear();
setValue('');
}

functiondeleteValue(){
NativeLocalStorage?.removeItem('myKey');
setValue('');
}

return(
<SafeAreaViewstyle={{flex:1}}>
<Textstyle={styles.text}>
Current stored value is: {value ??'No Value'}
</Text>
<TextInput
placeholder="Enter the text you want to store"
style={styles.textInput}
onChangeText={setEditingValue}
/>
<Buttontitle="Save"onPress={saveValue}/>
<Buttontitle="Delete"onPress={deleteValue}/>
<Buttontitle="Clear"onPress={clearAll}/>
</SafeAreaView>
);
}

const styles =StyleSheet.create({
text:{
margin:10,
fontSize:20,
},
textInput:{
margin:10,
height:40,
borderColor:'black',
borderWidth:1,
paddingLeft:5,
paddingRight:5,
borderRadius:5,
},
});

exportdefaultApp;

4. Write your Native Platform code

With everything prepared, we're going to start writing native platform code. We do this in 2 parts:

note

This guide shows you how to create a Turbo Native Module that only works with the New Architecture. If you need to support both the New Architecture and the Legacy Architecture, please refer to our backwards compatibility guide.

  • Android
  • iOS

Now it's time to write some Android platform code to make sure localStorage survives after the application is closed.

The first step is to implement the generated NativeLocalStorageSpec interface:

  • Java
  • Kotlin
android/app/src/main/java/com/nativelocalstorage/NativeLocalStorageModule.java
packagecom.nativelocalstorage;

importandroid.content.Context;
importandroid.content.SharedPreferences;
importcom.nativelocalstorage.NativeLocalStorageSpec;
importcom.facebook.react.bridge.ReactApplicationContext;

publicclassNativeLocalStorageModuleextendsNativeLocalStorageSpec{

publicstaticfinalStringNAME="NativeLocalStorage";

publicNativeLocalStorageModule(ReactApplicationContext reactContext){
super(reactContext);
}

@Override
publicStringgetName(){
returnNAME;
}

@Override
publicvoidsetItem(String value,String key){
SharedPreferences sharedPref =getReactApplicationContext().getSharedPreferences("my_prefs",Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPref.edit();
editor.putString(key, value);
editor.apply();
}

@Override
publicStringgetItem(String key){
SharedPreferences sharedPref =getReactApplicationContext().getSharedPreferences("my_prefs",Context.MODE_PRIVATE);
String username = sharedPref.getString(key,null);
return username;
}

@Override
publicvoidremoveItem(String key){
SharedPreferences sharedPref =getReactApplicationContext().getSharedPreferences("my_prefs",Context.MODE_PRIVATE);
sharedPref.edit().remove(key).apply();
}
}

Next we need to create NativeLocalStoragePackage. It provides an object to register our Module in the React Native runtime, by wrapping it as a Base Native Package:

  • Java
  • Kotlin
android/app/src/main/java/com/nativelocalstorage/NativeLocalStoragePackage.java
packagecom.nativelocalstorage;

importcom.facebook.react.BaseReactPackage;
importcom.facebook.react.bridge.NativeModule;
importcom.facebook.react.bridge.ReactApplicationContext;
importcom.facebook.react.module.model.ReactModuleInfo;
importcom.facebook.react.module.model.ReactModuleInfoProvider;

importjava.util.HashMap;
importjava.util.Map;

publicclassNativeLocalStoragePackageextendsBaseReactPackage{

@Override
publicNativeModulegetModule(String name,ReactApplicationContext reactContext){
if(name.equals(NativeLocalStorageModule.NAME)){
returnnewNativeLocalStorageModule(reactContext);
}else{
returnnull;
}
}

@Override
publicReactModuleInfoProvidergetReactModuleInfoProvider(){
returnnewReactModuleInfoProvider(){
@Override
publicMap<String,ReactModuleInfo>getReactModuleInfos(){
Map<String,ReactModuleInfo> map =newHashMap<>();
map.put(NativeLocalStorageModule.NAME,newReactModuleInfo(
NativeLocalStorageModule.NAME,// name
NativeLocalStorageModule.NAME,// className
false,// canOverrideExistingModule
false,// needsEagerInit
false,// isCXXModule
true// isTurboModule
));
return map;
}
};
}
}

Finally, we need to tell the React Native in our main application how to find this Package. We call this "registering" the package in React Native.

In this case, you add it to be returned by the getPackages method.

info

Later you’ll learn how to distribute your Native Modules as npm packages, which our build tooling will autolink for you.

  • Java
  • Kotlin
android/app/src/main/java/com/turobmoduleexample/MainApplication.java
packagecom.inappmodule;

importandroid.app.Application;
importcom.facebook.react.PackageList;
importcom.facebook.react.ReactApplication;
importcom.facebook.react.ReactHost;
importcom.facebook.react.ReactNativeHost;
importcom.facebook.react.ReactPackage;
importcom.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
importcom.facebook.react.defaults.DefaultReactHost;
importcom.facebook.react.defaults.DefaultReactNativeHost;
importcom.facebook.soloader.SoLoader;
importcom.nativelocalstorage.NativeLocalStoragePackage;

importjava.util.ArrayList;
importjava.util.List;

publicclassMainApplicationextendsApplicationimplementsReactApplication{

privatefinalReactNativeHost reactNativeHost =newDefaultReactNativeHost(this){
@Override
publicList<ReactPackage>getPackages(){
List<ReactPackage> packages =newPackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage());
packages.add(newNativeLocalStoragePackage());
return packages;
}

@Override
publicStringgetJSMainModuleName(){
return"index";
}

@Override
publicbooleangetUseDeveloperSupport(){
returnBuildConfig.DEBUG;
}

@Override
publicbooleanisNewArchEnabled(){
returnBuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
}

@Override
publicbooleanisHermesEnabled(){
returnBuildConfig.IS_HERMES_ENABLED;
}
};

@Override
publicReactHostgetReactHost(){
returnDefaultReactHost.getDefaultReactHost(getApplicationContext(), reactNativeHost);
}

@Override
publicvoidonCreate(){
super.onCreate();
SoLoader.init(this,false);
if(BuildConfig.IS_NEW_ARCHITECTURE_ENABLED){
// If you opted-in for the New Architecture, we load the native entry point for this app.
DefaultNewArchitectureEntryPoint.load();
}
}
}

You can now build and run your code on an emulator:

  • npm
  • Yarn
bash
npm run android

AltStyle によって変換されたページ (->オリジナル) /