This doc contains the logs of the steps done to achieve the final result.
- [Setup] Create the example-library folder and the package.json
- [Native Module] Create the JS import
- [Native Module] Create the iOS implementation
- [Native Module] Create the Android implementation
- [Native Module] Test The Native Module
- [TurboModule] Add the JavaScript specs
- [TurboModule] Set up CodeGen - iOS
- [TurboModule] Set up CodeGen - Android
- [TurboModule] Set up podspec file
- [TurboModule] Update the Native iOS code
- [TurboModule] Android: Convert ReactPackage to a backward compatible TurboReactPackage
- [TurboModule] Android: Update the Native code to use two sourcesets
- [TurboModule] Android: Refactor the code to use a shared implementation
- [TurboModule] Unify JavaScript interface
- [TurboModule] Test the Turbomodule
mkdir example-library
touch example-library/package.json
- Paste the following code into the
package.json
file
{
"name": "example-library",
"version": "0.0.1",
"description": "Showcase Turbomodule with backward compatibility",
"react-native": "src/index",
"source": "src/index",
"files": [
"src",
"android",
"ios",
"example-library.podspec",
"!android/build",
"!ios/build",
"!**/__tests__",
"!**/__fixtures__",
"!**/__mocks__"
],
"keywords": ["react-native", "ios", "android"],
"repository": "https://github.com/<your_github_handle>/example-library",
"author": "<Your Name> <your_email@your_provider.com> (https://github.com/<your_github_handle>)",
"license": "MIT",
"bugs": {
"url": "https://github.com/<your_github_handle>/example-library/issues"
},
"homepage": "https://github.com/<your_github_handle>/example-library#readme",
"devDependencies": {},
"peerDependencies": {
"react": "*",
"react-native": "*"
}
}
mkdir example-library/src
touch example-library/src/index.js
- Paste the following content into the
index.js
// @flow
import { NativeModules } from 'react-native'
export default NativeModules.Calculator;
mkdir example-library/ios
- Open Xcode
- Create a new static library in the
ios
folder calledRNCalculator
. Keep Objective-C as language. - Make sure that the
Create Git repository on my mac
option is unchecked - Open finder and arrange the files and folder as shown below:
It is important that the
example-library '-> ios '-> RNCalculator '-> RNCalculator.h '-> RNCalculator.m '-> RNCalculator.xcodeproj
RNCalculator.xcodeproj
is a direct child of theexample-library/ios
folder. - Open the
RNCalculator.h
file and update the code as it follows:- #import <Foundation/Foundation.h> + #import <React/RCTBridgeModule.h> + @interface RNCalculator : NSObject <RCTBridgeModule> @end
- Open the
RNCalculator.m
file and replace the code with the following:#import "RNCalculator.h" @implementation RNCalculator RCT_EXPORT_MODULE(Calculator) RCT_REMAP_METHOD(add, addA:(NSInteger)a andB:(NSInteger)b withResolver:(RCTPromiseResolveBlock) resolve withRejecter:(RCTPromiseRejectBlock) reject) { NSNumber *result = [[NSNumber alloc] initWithInteger:a+b]; resolve(result); } @end
- In the
example-library
folder, create aexample-library.podspec
file - Copy this code in the
podspec
file
require "json"
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
Pod::Spec.new do |s|
s.name = "example-library"
s.version = package["version"]
s.summary = package["description"]
s.description = package["description"]
s.homepage = package["homepage"]
s.license = package["license"]
s.platforms = { :ios => "11.0" }
s.author = package["author"]
s.source = { :git => package["repository"], :tag => "#{s.version}" }
s.source_files = "ios/**/*.{h,m,mm,swift}"
s.dependency "React-Core"
end
- Create a folder
example-library/android
- Create a file
example-library/android/build.gradle
and add this code:buildscript { ext.safeExtGet = {prop, fallback -> rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback } repositories { google() gradlePluginPortal() } dependencies { classpath("com.android.tools.build:gradle:7.0.4") } } apply plugin: 'com.android.library' android { compileSdkVersion safeExtGet('compileSdkVersion', 31) defaultConfig { minSdkVersion safeExtGet('minSdkVersion', 21) targetSdkVersion safeExtGet('targetSdkVersion', 31) } } repositories { maven { // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm url "$projectDir/../node_modules/react-native/android" } mavenCentral() google() } dependencies { implementation 'com.facebook.react:react-native:+' }
- Create a file
example-library/android/src/main/AndroidManifest.xml
and add this code:<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.rnnewarchitecturelibrary"> </manifest>
- Create a file
example-library/android/src/main/java/com/rnnewarchitecturelibrary/CalculatorModule.java
and add this code:package com.rnnewarchitecturelibrary; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import java.util.Map; import java.util.HashMap; public class CalculatorModule extends ReactContextBaseJavaModule { CalculatorModule(ReactApplicationContext context) { super(context); } @Override public String getName() { return "Calculator"; } @ReactMethod public void add(int a, int b, Promise promise) { promise.resolve(a + b); } }
- Create a file
example-library/android/src/main/java/com/rnnewarchitecturelibrary/CalculatorPackage.java
and add this code:package com.rnnewarchitecturelibrary; import com.facebook.react.ReactPackage; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.uimanager.ViewManager; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class CalculatorPackage implements ReactPackage { @Override public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { return Collections.emptyList(); } @Override public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) { List<NativeModule> modules = new ArrayList<>(); modules.add(new CalculatorModule(reactContext)); return modules; } }
- At the same level of example-library run
npx react-native init OldArchitecture
cd OldArchitecture && yarn add ../example-library
- Open
OldArchitecture/App.js
file and replace the content with:/** * Sample React Native App * https://github.com/facebook/react-native * * @format * @flow strict-local */ import React from 'react'; import {useState} from "react"; import type {Node} from 'react'; import { SafeAreaView, StatusBar, Text, Button, } from 'react-native'; import Calculator from 'example-library/src/index' const App: () => Node = () => { const [currentResult, setResult] = useState<number | null>(null); return ( <SafeAreaView> <StatusBar barStyle={'dark-content'}/> <Text style={{marginLeft:20, marginTop:20}}>3+7={currentResult ?? "??"}</Text> <Button title="Compute" onPress={async () => { const result = await Calculator.add(3, 7); setResult(result); }} /> </SafeAreaView> ); }; export default App;
- To run the App on iOS, install the dependencies:
cd ios && pod install && cd ..
npx react-native start
(In another terminal, to run Metro)- Run the app
- if using iOS:
npx react-native run-ios
- if using Android:
npx react-native run-android
- if using iOS:
- Click on the
Compute
button and see the app working
Note: OldArchitecture app has not been committed not to pollute the repository.
touch example-library/src/NativeCalculator.js
- Paste the following code:
// @flow import type { TurboModule } from 'react-native/Libraries/TurboModule/RCTExport'; import { TurboModuleRegistry } from 'react-native'; export interface Spec extends TurboModule { // your module methods go here, for example: add(a: number, b: number): Promise<number>; } export default (TurboModuleRegistry.get<Spec>( 'Calculator' ): ?Spec);
- Open the
example-library/package.json
- Add the following snippet at the end of it:
, "codegenConfig": { "libraries": [ { "name": "RNCalculatorSpec", "type": "modules", "jsSrcsDir": "src" } ] }
- Open the
example-library/android/build.gradle
file and update the code as follows:+ def isNewArchitectureEnabled() { + return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true" +} apply plugin: 'com.android.library' +if (isNewArchitectureEnabled()) { + apply plugin: 'com.facebook.react' +} // ... other parts of the build file dependencies { implementation 'com.facebook.react:react-native:+' } + if (isNewArchitectureEnabled()) { + react { + jsRootDir = file("../src/") + libraryName = "calculator" + codegenJavaPackageName = "com.rnnewarchitecturelibrary" + } + }
- Open the
example-library/example-library.podspec
file - Before the
Pod::Spec.new do |s|
add the following code:folly_version = '2021.06.28.00-v2' folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'
- Before the
end
tag, add the following code# This guard prevent to install the dependencies when we run `pod install` in the old architecture. if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1" s.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"", "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" } s.dependency "React-Codegen" s.dependency "RCT-Folly", folly_version s.dependency "RCTRequired" s.dependency "RCTTypeSafety" s.dependency "ReactCommon/turbomodule/core" end
- In the
ios/RNCalculator
folder, rename theRNCalculator.m
intoRNCalculator.mm
- Open it and add the following
import
:// Thanks to this guard, we won't import this header when we build for the old architecture. #ifdef RCT_NEW_ARCH_ENABLED #import "RNCalculatorSpec.h" #endif
- Then, before the
@end
keyword, add the following code:// Thanks to this guard, we won't compile this code when we build for the old architecture. #ifdef RCT_NEW_ARCH_ENABLED - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule: (const facebook::react::ObjCTurboModule::InitParams &)params { return std::make_shared<facebook::react::NativeCalculatorSpecJSI>(params); } #endif
- Open the
example-library/android/src/main/java/com/rnnewarchitecturelibrary/CalculatorModule.java
and modify it as it follows:public class CalculatorModule extends ReactContextBaseJavaModule { + public static final String NAME = "Calculator"; CalculatorModule(ReactApplicationContext context) { super(context); } @Override public String getName() { - return "Calculator"; + return NAME; }
- Open the
example-library/android/src/main/java/com/rnnewarchitecturelibrary/CalculatorPackage.java
and replace its content with:package com.rnnewarchitecturelibrary; import androidx.annotation.Nullable; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.module.model.ReactModuleInfo; import com.facebook.react.module.model.ReactModuleInfoProvider; import com.facebook.react.TurboReactPackage; import com.facebook.react.uimanager.ViewManager; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.HashMap; import java.util.Map; public class CalculatorPackage extends TurboReactPackage { @Nullable @Override public NativeModule getModule(String name, ReactApplicationContext reactContext) { if (name.equals(CalculatorModule.NAME)) { return new CalculatorModule(reactContext); } else { return null; } } @Override public ReactModuleInfoProvider getReactModuleInfoProvider() { return () -> { final Map<String, ReactModuleInfo> moduleInfos = new HashMap<>(); moduleInfos.put( CalculatorModule.NAME, new ReactModuleInfo( CalculatorModule.NAME, CalculatorModule.NAME, false, // canOverrideExistingModule false, // needsEagerInit true, // hasConstants false, // isCxxModule false // isTurboModule )); return moduleInfos; }; } }
- Open the
example-library/android/build.gradle
file and update the code as it follows:defaultConfig { minSdkVersion safeExtGet('minSdkVersion', 21) targetSdkVersion safeExtGet('targetSdkVersion', 31) + buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString() + } + + sourceSets { + main { + if (isNewArchitectureEnabled()) { + java.srcDirs += ['src/newarch'] + } else { + java.srcDirs += ['src/oldarch'] + } + } } }
- Open the
example-library/android/src/main/java/com/rnnewarchitecturelibrary/CalculatorPackage.java
and update thegetReactModuleInfoProvider
function as it follows:public ReactModuleInfoProvider getReactModuleInfoProvider() { return () -> { final Map<String, ReactModuleInfo> moduleInfos = new HashMap<>(); + boolean isTurboModule = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; moduleInfos.put( CalculatorModule.NAME, new ReactModuleInfo( CalculatorModule.NAME, CalculatorModule.NAME, false, // canOverrideExistingModule false, // needsEagerInit true, // hasConstants false, // isCxxModule - false, // isTurboModule + isTurboModule // isTurboModule )); return moduleInfos; };
- Create a file
example-library/android/src/newarch/java/com/rnnewarchitecturelibrary/CalculatorModule.java
(notice thenewarch
child of thesrc
folder) and paste the following code:package com.rnnewarchitecturelibrary; import androidx.annotation.NonNull; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import java.util.Map; import java.util.HashMap; public class CalculatorModule extends NativeCalculatorSpec { public static final String NAME = "Calculator"; CalculatorModule(ReactApplicationContext context) { super(context); } @Override @NonNull public String getName() { return NAME; } @Override public void add(double a, double b, Promise promise) { promise.resolve(a + b); } }
- Create a file
example-library/android/src/oldarch/java/com/rnnewarchitecturelibrary/CalculatorModule.java
(notice theoldarch
child of thesrc
folder) and paste the following code:package com.rnnewarchitecturelibrary; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import java.util.Map; import java.util.HashMap; public class CalculatorModule extends ReactContextBaseJavaModule { public static final String NAME = "Calculator"; CalculatorModule(ReactApplicationContext context) { super(context); } @Override public String getName() { return NAME; } @ReactMethod public void add(int a, int b, Promise promise) { promise.resolve(a + b); } }
- Create a new
example-library/android/src/main/java/com/rnnewarchitecturelibrary/CalculatorModuleImpl.java
file (notice that thesrc
's subfolder is nowmain
) and paste the following code:package com.rnnewarchitecturelibrary; import androidx.annotation.NonNull; import com.facebook.react.bridge.Promise; import java.util.Map; import java.util.HashMap; public class CalculatorModuleImpl { public static final String NAME = "Calculator"; public static void add(double a, double b, Promise promise) { promise.resolve(a + b); } }
- Open the
example-library/android/src/main/java/com/rnnewarchitecturelibrary/CalculatorPackage.java
file and replace all the instances ofCalculatorModule
withCalculatorModuleImpl
- Open the
example-library/android/src/newarch/java/com/rnnewarchitecturelibrary/CalculatorModule.java
file and update it as it follows:public class CalculatorModule extends NativeCalculatorSpec { - public static final String NAME = "Calculator"; CalculatorModule(ReactApplicationContext context) { super(context); } @Override @NonNull public String getName() { - return NAME; + return CalculatorModuleImpl.NAME; } @Override public void add(double a, double b, Promise promise) { - promise.resolve(a + b); + CalculatorModuleImpl.add(a, b, promise); } }
- Open the
example-library/android/src/oldarch/java/com/rnnewarchitecturelibrary/CalculatorModule.java
and update it as it follows:public class CalculatorModule extends ReactContextBaseJavaModule { - public static final String NAME = "Calculator"; CalculatorModule(ReactApplicationContext context) { super(context); } @Override public String getName() { - return NAME; + return CalculatorModuleImpl.NAME; } @ReactMethod public void add(int a, int b, Promise promise) { - promise.resolve(a + b); + CalculatorModuleImpl.add(a, b, promise); } }
- Open the
src/index.js
file - Replace the code with the following:
// @flow import { NativeModules } from 'react-native' const isTurboModuleEnabled = global.__turboModuleProxy != null; const calculator = isTurboModuleEnabled ? require("./NativeCalculator").default : NativeModules.Calculator; export default calculator;
- At the same level of example-library run
npx react-native init NewArchitecture --version next
(next
takes the next version that is about to be released. Any version >= 0.68 should work) cd NewArchitecture && yarn add ../example-library
- Open
NewArchitecture/App.js
file and replace the content with the same file used for theOldArchitecture
. - To run the App on iOS, install the dependencies:
cd ios && pod install && cd ..
npx react-native start
(In another terminal, to run Metro)- Run the app:
- iOS:
npx react-native run-ios
- Android
npx react-native run-android
- iOS:
- Click on the
Compute
button and see the app working
Note: NewArchitecture app has not been committed not to pollute the repository.
Autolinking doesn’t work with the new architecture out of the box. Therefore you need to ask the user of your library to do the following steps:
- Open the
NewArchitecture/android/app/build.gradle
file and update the file as it follows:"PROJECT_BUILD_DIR=$buildDir", "REACT_ANDROID_DIR=$rootDir/../node_modules/react-native/ReactAndroid", - "REACT_ANDROID_BUILD_DIR=$rootDir/../node_modules/react-native/ReactAndroid/build" + "REACT_ANDROID_BUILD_DIR=$rootDir/../node_modules/react-native/ReactAndroid/build", + "NODE_MODULES_DIR=$rootDir/../node_modules/" cFlags "-Wall", "-Werror", "-fexceptions", "-frtti", "-DWITH_INSPECTOR=1" cppFlags "-std=c++17"
- Open the
NewArchitecture/android/app/src/main/jni/Android.mk
file and update the file as it follows:# If you wish to add a custom TurboModule or Fabric component in your app you # will have to include the following autogenerated makefile. # include $(GENERATED_SRC_DIR)/codegen/jni/Android.mk + + # Includes the MK file for `example-library` + + include $(NODE_MODULES_DIR)/example-library/android/build/generated/source/codegen/jni/Android.mk include $(CLEAR_VARS)
- In the same file above, go to the
LOCAL_SHARED_LIBRARIES
setting and add the following line:libreact_codegen_rncore \ + libreact_codegen_calculator \ libreact_debug \
- Open the
NewArchitecture/android/app/src/main/jni/MainApplicationModuleProvider.cpp
file and update the file as it follows:- Add the import for the calculator:
#include <answersolver.h> + #include <calculator.h>
- Add the following check in the
MainApplicationModuleProvider
constructor:// auto module = samplelibrary_ModuleProvider(moduleName, params); // if (module != nullptr) { // return module; // } + auto module = calculator_ModuleProvider(moduleName, params); + if (module != nullptr) { + return module; + } return rncore_ModuleProvider(moduleName, params); }
- Add the import for the calculator: