- [Setup] Create the calculator 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
- [TurboModule] Set up build.gradle
- [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 calculator
touch calculator/package.json
- Paste the following code into the
package.json
file
{
"name": "calculator",
"version": "0.0.1",
"description": "Showcase Turbomodule with backward compatibility",
"react-native": "src/index",
"source": "src/index",
"files": [
"src",
"android",
"ios",
"calculator.podspec",
"!android/build",
"!ios/build",
"!**/__tests__",
"!**/__fixtures__",
"!**/__mocks__"
],
"keywords": ["react-native", "ios", "android"],
"repository": "https://github.com/<your_github_handle>/calculator",
"author": "<Your Name> <your_email@your_provider.com> (https://github.com/<your_github_handle>)",
"license": "MIT",
"bugs": {
"url": "https://github.com/<your_github_handle>/calculator/issues"
},
"homepage": "https://github.com/<your_github_handle>/calculator#readme",
"devDependencies": {},
"peerDependencies": {
"react": "*",
"react-native": "*"
}
}
mkdir calculator/src
touch calculator/src/index.js
- Paste the following content into the
index.js
// @flow
import { NativeModules } from 'react-native'
export default NativeModules.Calculator;
mkdir calculator/ios
- Create an
ios/RNCalculator.h
file and fill it with the following code:#import <Foundation/Foundation.h> #import <React/RCTBridgeModule.h> @interface RNCalculator : NSObject <RCTBridgeModule> @end
- Create an
ios/RNCalculator.m
file and replace the code with the following:#import "RNCalculator.h" @implementation RNCalculator RCT_EXPORT_MODULE() 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
calculator
folder, create acalculator.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 = "calculator"
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
calculator/android
- Create a file
calculator/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
calculator/android/src/main/AndroidManifest.xml
and add this code:<manifest xmlns:android="https://schemas.android.com/apk/res/android" package="com.rnnewarchitecturelibrary"> </manifest>
- Create a file
calculator/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 "RNCalculator"; } @ReactMethod public void add(int a, int b, Promise promise) { promise.resolve(a + b); } }
- Create a file
calculator/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 calculator run
npx react-native init OldArchitecture --version 0.70.0-rc.2
cd OldArchitecture && yarn add ../calculator
- 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 RNCalculator from 'calculator/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 RNCalculator.add(3, 7); setResult(result); }} /> </SafeAreaView> ); }; export default App;
- To run the App on iOS, install the dependencies:
cd ios && bundle install && bundle exec pod install && cd ..
- 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 calculator/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>( 'RNCalculator' ): ?Spec);
- Open the
calculator/package.json
- Add the following snippet at the end of it:
, "codegenConfig": { "name": "RNCalculatorSpec", "type": "modules", "jsSrcsDir": "src", "android": { "javaPackageName": "com.rnnewarchitecturelibrary" } }
- Open the
calculator/android/build.gradle
file and update the code as follows:+ def isNewArchitectureEnabled() { + return project.hasProperty("newArchEnabled") && project.newArchEnabled == "carview.php?tsp=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:+' }
- Open the
calculator/calculator.podspec
file - Before the
Pod::Spec.new do |s|
add the following code:folly_version = '2021.07.22.00' 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 replace its content with:
#import "RNCalculator.h" // 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 @implementation RNCalculator RCT_EXPORT_MODULE() RCT_REMAP_METHOD(add, addA:(NSInteger)a andB:(NSInteger)b withResolver:(RCTPromiseResolveBlock) resolve withRejecter:(RCTPromiseRejectBlock) reject) { return [self add:a b:b resolve:resolve reject:reject]; } // 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 - (void)add:(double)a b:(double)b resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { NSNumber *result = [[NSNumber alloc] initWithInteger:a+b]; resolve(result); } @end
- Open the
ios/RNCalculator.h
file and replace its content with:#import <Foundation/Foundation.h> #ifdef RCT_NEW_ARCH_ENABLED #import <RNCalculatorSpec/RNCalculatorSpec.h> @interface RNCalculator: NSObject <NativeCalculatorSpec> #else #import <React/RCTBridgeModule.h> @interface RNCalculator : NSObject <RCTBridgeModule> #endif @end
- Open the
calculator/android/src/main/java/com/rnnewarchitecturelibrary/CalculatorModule.java
and modify it as it follows:public class CalculatorModule extends ReactContextBaseJavaModule { + public static final String NAME = "RNCalculator"; CalculatorModule(ReactApplicationContext context) { super(context); } @Override public String getName() { - return "Calculator"; + return NAME; }
- Open the
calculator/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
calculator/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
calculator/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
calculator/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 = "RNCalculator"; 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
calculator/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 = "RNCalculator"; 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
calculator/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 = "RNCalculator"; public static void add(double a, double b, Promise promise) { promise.resolve(a + b); } }
- Open the
calculator/android/src/main/java/com/rnnewarchitecturelibrary/CalculatorPackage.java
file and update the following lines:public NativeModule getModule(String name, ReactApplicationContext reactContext) { - if (name.equals(CalculatorModule.NAME)) { + if (name.equals(CalculatorModuleImpl.NAME)) { return new CalculatorModule(reactContext); } else { return null; } } @Override public ReactModuleInfoProvider getReactModuleInfoProvider() { return () -> { final Map<String, ReactModuleInfo> moduleInfos = new HashMap<>(); boolean isTurboModule = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; moduleInfos.put( - CalculatorModule.NAME, + CalculatorModuleImpl.NAME, new ReactModuleInfo( - CalculatorModule.NAME, - CalculatorModule.NAME, + CalculatorModuleImpl.NAME, + CalculatorModuleImpl.NAME, false, // canOverrideExistingModule false, // needsEagerInit true, // hasConstants false, // isCxxModule isTurboModule // isTurboModule )); return moduleInfos; }; }
- Open the
calculator/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 = "RNCalculator"; 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
calculator/android/src/oldarch/java/com/rnnewarchitecturelibrary/CalculatorModule.java
and update it as it follows:public class CalculatorModule extends ReactContextBaseJavaModule { - public static final String NAME = "RNCalculator"; 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); } }
- Remove the
android/src/main/java/com/rnnewarchitecturelibrary/CalculatorModule.java
(the one in themain
folder).
- Open the
src/index.js
file - Replace the code with the following:
// @flow export default require("./NativeCalculator").default;
- At the same level of calculator run
npx react-native init NewArchitecture --version 0.70.0-rc.2
cd NewArchitecture && yarn add ../calculator
- 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 && bundle install && RCT_NEW_ARCH_ENABLED=1 bundle exec pod install && cd ..
- Open the
NewArchitecture/android/gradle.properties
and update them as it follows:- newArchEnabled=false + newArchEnabled=true
- 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.