GitHub - react-native-community/RNNewArchitectureLibraries at feat/back-turbomodule-070 (original) (raw)
Table of contents
- [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
Steps
[Setup] Create the calculator folder and the package.json
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//calculator", "author": " <your_email@your_provider.com> (https://github.com/)", "license": "MIT", "bugs": { "url": "https://github.com//calculator/issues" }, "homepage": "https://github.com//calculator#readme", "devDependencies": {}, "peerDependencies": { "react": "", "react-native": "" } }
[Native Module] Create the JS import
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;
[Native Module] Create the iOS implementation
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
@end
3. 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
4. In the calculator
folder, create a calculator.podspec
file
5. 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
[Native Module] Create the Android implementation
- 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:
[Native Module] Test The Native Module
- 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 ( 3+7={currentResult ?? "??"} { const result = await RNCalculator.add(3, 7); setResult(result); }} /> ); }; 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.
[TurboModule] Add the JavaScript specs
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;
}
export default (TurboModuleRegistry.get(
'RNCalculator'
): ?Spec);
[TurboModule] Set up CodeGen
- 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"
}
}
[TurboModule] Set up build.gradle
- Open the
calculator/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:+'
}
[TurboModule] Set up podspec file
- 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
[TurboModule] Update the Native iOS code
- 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_ptrfacebook::react::TurboModule)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return std::make_sharedfacebook::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
#else
#import <React/RCTBridgeModule.h>
@interface RNCalculator : NSObject
#endif
@end
[TurboModule] Android: Convert ReactPackage to a backward compatible TurboReactPackage
- 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;
};
}
}
[TurboModule] Android: Update the Native code to use two sourcesets
- 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']
}
}
}
}
2. Open the calculator/android/src/main/java/com/rnnewarchitecturelibrary/CalculatorPackage.java
and update the getReactModuleInfoProvider
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);
}
}
[TurboModule] Android: Refactor the code to use a shared implementation
- 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).
[TurboModule] Unify JavaScript interface
- Open the
src/index.js
file - Replace the code with the following:
// @flow
export default require("./NativeCalculator").default;
[TurboModule] Test the Turbomodule
- 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 the OldArchitecture. - 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.