From a764958839e3abc8a699779500bc951722428047 Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Wed, 20 Jul 2022 07:51:37 -0700 Subject: [PATCH] [FEAT] TurboModules guide (#3168) * [Feat] Add intro for Fabric Components * feat: add guide to create a Fabric Component * Beginning of guide/folder structure * WIP JS Spec * specification section * Configuration * native code intro * Must be named Spec * Best stab at iOS native code, but I don't know how to describe what's going on in the code very well. Extrapolated what I could. * Android instructions iOS isn't working for me. Builds, but can't load module. Writing up Android auto-linking next because the steps I tested did work. * Include linking instructions from RNNArch repo * Add example JavaScript * native modules link * Address quick feedback items * Remove, fix for rebased branch * fix TM parameter on Android * Revert to 'Codegen' casing * Revert folly version change 2021.07.22 is for current version on main * fix typo * getTurboModule explainer * Sentence edits - Fix acronym bolding - Change wording to "recommended" because "standard" has other connotations of possibly being required - Parentheses unnecessary, distracting * Remove TODO for now Getting inconsistent results here, not sure if this is wrong or not; removing TODO for now so it doesn't block anything * ABI rephrase, more in line with new Fabric guide wording * Explain shared C++ code more * feat: add guide to create a Fabric Component * feat: add guide to create a Fabric Component * package.json description * Lint fixes * fix: Move JS constants to reduce changes * fix: Remove newline * feat: add required step for Android Codegen * fix: use the proper links Co-authored-by: Riccardo Cipolleschi --- docs/the-new-architecture/pillars-codegen.md | 10 +- .../pillars-fabric-components.md | 2 +- .../pillars-turbomodule.md | 766 +++++++++++++++++- 3 files changed, 763 insertions(+), 15 deletions(-) diff --git a/docs/the-new-architecture/pillars-codegen.md b/docs/the-new-architecture/pillars-codegen.md index e6cb79240b5..ec6532e0e6c 100644 --- a/docs/the-new-architecture/pillars-codegen.md +++ b/docs/the-new-architecture/pillars-codegen.md @@ -7,7 +7,7 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import con The **Codegen** is not a proper pillar, but it is a tool that can be used to avoid writing of a lot of repetitive code. Using the **Codegen** is not mandatory: all the code that is generated by it can also be written manually. However, it generates scaffolding code that could save you a lot of time. -The **Codegen** is invoked automatically by React Native every time an iOS or an Android app is built. Occasionally, you would like to run the scripts that generate the code manually to know which types and files are actually generated: this is a common scenario when developing [**TurboModules**](pillars-turbomodules) and [**Fabric Components**](pillars-fabric-components), for example. +The **Codegen** is invoked automatically by React Native every time an iOS or an Android app is built. Occasionally, you would like to run the scripts that generate the code manually to know which types and files are actually generated: this is a common scenario when developing [**TurboModules**](./pillars-turbomodules) and [**Fabric Components**](./pillars-fabric-components), for example. This guide teaches how to configure the **Codegen**, how to invoke it manually for each platform, and it describes the generated code. @@ -34,7 +34,7 @@ Then, add the module that requires the **Codegen** as an NPM dependency of the a yarn add ``` -See how to create a [TurboModule](pillars-turbomodule) or a [Fabric Component](pillar-fabric-components) to get more information on how to configure them. +See how to create a [TurboModule](pillars-turbomodules) or a [Fabric Component](pillars-fabric-components) to get more information on how to configure them. The rest of this guide assumes that you have a `TurboModule` and/or a `Fabric Component` properly set up. @@ -44,7 +44,7 @@ The rest of this guide assumes that you have a `TurboModule` and/or a `Fabric Co The **Codegen** for iOS relies on some Node scripts that are invoked during the build process. The scripts are located in the `MyApp/node_modules/react_native/scripts/` folder. -The script that you have to run is the `generate-artifacts.js` script. This searches among all the dependencies of the app, looking for JS files which respects some specific conventions (look at [TurboModules](pillars-turbomodule) and [Fabric Components](pillar-fabric-components) sections for details) and it generates the required code. +The script that you have to run is the `generate-artifacts.js` script. This searches among all the dependencies of the app, looking for JS files which respects some specific conventions (look at [TurboModules](pillars-turbomodules) and [Fabric Components](pillars-fabric-components) sections for details) and it generates the required code. To invoke the script you can run this command from the root folder of your app: @@ -120,7 +120,7 @@ The content of each Fabric Component folder contains several files. The basic el Additionally, the **Codegen** also creates a `ComponentDescriptor.h` and an `RCTComponentViewHelpers.h` files: the first one is used by React Native and Fabric to properly get a reference to the Component, while the latter contains some helper methods and protocols that can be implemented by the Native View to properly respond to JSI invocations. -For further details about how Fabric works, have a look at the [Renderer](../../architecture/fabric-renderer) section. +For further details about how Fabric works, have a look at the [Renderer](/architecture/fabric-renderer) section. ### RCTThirdPartyFabricComponentsProvider @@ -220,7 +220,7 @@ Then, it generates the C++ files in the `jni` folder. They follow the same iOS c ### Fabric Component -The **Codegen** for a Fabric Component contains a `MyFabricComponentManagerInterface.java` and a `MyFabricComponentManagerDelegate.java` in the `java` package. They are implemented and used by the native `MyFabricComponentManager` required to properly load the component at runtime (See the guide on how to create a [Fabric Component](./pillar-fabric-components) for details). +The **Codegen** for a Fabric Component contains a `MyFabricComponentManagerInterface.java` and a `MyFabricComponentManagerDelegate.java` in the `java` package. They are implemented and used by the native `MyFabricComponentManager` required to properly load the component at runtime (See the guide on how to create a [Fabric Component](./pillars-fabric-components) for details). Then, there is a layer of JNI C++ files that are used by Fabric to render the components. The basic element for a Fabric Componenent is the `ShadowNode`: it represents a node in the React absract tree. The `ShadowNode` represents a React entity, therefore it could need some props, which are defined in the `Props` files and, sometimes, an `EventEmitter`, defined in the corresponding file. diff --git a/docs/the-new-architecture/pillars-fabric-components.md b/docs/the-new-architecture/pillars-fabric-components.md index 949205e4c03..f04f75d9387 100644 --- a/docs/the-new-architecture/pillars-fabric-components.md +++ b/docs/the-new-architecture/pillars-fabric-components.md @@ -605,7 +605,7 @@ To generate the code, you need to manually invoke **Codegen**. This is done simi cd MyApp yarn add ../RTNCenteredText cd android -./gradlew generateCodegenArtifactsFromSchema --rerun-tasks +./gradlew generateCodegenArtifactsFromSchema ``` This script first adds the package to the app, in the same way iOS does. Then, after moving to the `android` folder, it invokes a Gradle task to generate the scaffolding code. diff --git a/docs/the-new-architecture/pillars-turbomodule.md b/docs/the-new-architecture/pillars-turbomodule.md index ee305ee1656..07155eec315 100644 --- a/docs/the-new-architecture/pillars-turbomodule.md +++ b/docs/the-new-architecture/pillars-turbomodule.md @@ -3,16 +3,764 @@ id: pillars-turbomodules title: TurboModules --- -This section contains a high-level introduction to TurboModules. It provides enough context to understand when a TurboModule is needed and how it roughly works. +import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import constants from '@site/core/TabsConstants'; -This section must have a warning that it works only with the new architecture enabled. It points to the [migration section](../new-architecture-intro). +If you've worked with React Native, you may be familiar with the concept of [Native Modules](../native-modules-intro.md), which allow JavaScript and platform-native code to communicate over the React Native "bridge", which handles cross-platform serialization via JSON. -## How to create a Turbomodule +TurboModules are the next iteration on Native Modules that provide a few extra [benefits](./why): -This section is a step-by-step guide to create a TurboModule from scratch. The list of subsections is roughly: +- Strongly typed interfaces that are consistent across platforms +- The ability to write your code in C++, either exclusively or integrated with another native platform language, reducing the need to duplicate implementations across platforms +- Lazy loading of modules, allowing for faster app startup +- The use of JSI, a JavaScript interface for native code, which allows for more efficient communication between native and JavaScript code than the bridge -- JS spec (with all the supported features) -- Configuration (package.json, cocoapods, gradle, …) and CodeGen -- Native code (one section for iOS and one for Android) -- Integration in an App (`yarn add` and how to connect the JS specs to the app itself) -- Troubleshooting (common issues and how to solve them) +This guide will show you how to create a basic TurboModule. + +:::caution +TurboModules only work with the **New Architecture** enabled. +To migrate to the **New Architecture**, follow the [Migration guide](../new-architecture-intro) +::: + +## How to Create a TurboModule + +To create a TurboModule, we need to: + +1. Define the JavaScript specification. +2. Configure the module so that Codegen can generate the scaffolding. +3. Write the native code to finish implementing the module. + +## 1. Folder Setup + +In order to keep the module decoupled from the app, it's a good idea to define the module separately from the app, and then add it as a dependency to your app later. This is also what you'll do for writing TurboModules that can be released as open-source libraries later. + +Next to your application, create a folder called `RTNCalculator`. **RTN** stands for "**R**eac**t** **N**ative", and is a recommended prefix for React Native modules. + +Within `RTNCalculator`, create three subfolders: `js`, `ios`, and `android`. + +The final result should look like this: + +```sh +TurboModulesGuide +├── MyApp +└── RTNCalculator + ├── android + ├── ios + └── js +``` + +## 2. JavaScript Specification + +The **New Architecture** requires interfaces specified in a typed dialect of JavaScript (either [Flow](https://flow.org/) or [TypeScript](https://www.typescriptlang.org/)). **Codegen** will use these specifications to generate code in strongly-typed languages, including C++, Objective-C++, and Java. + +There are two requirements the file containing this specification must meet: + +1. The file **must** be named `Native`, with a `.js` or `.jsx` extension when using Flow, or a `.ts`, or `.tsx` extension when using TypeScript. Codegen will only look for files matching this pattern. +2. The file must export a `TurboModuleRegistrySpec` object. + + + + +```typescript title="NativeCalculator.js" +// @flow +import type { TurboModule } from 'react-native/Libraries/TurboModule/RCTExport'; +import { TurboModuleRegistry } from 'react-native'; + +export interface Spec extends TurboModule { + add(a: number, b: number): Promise; +} +export default (TurboModuleRegistry.get( + 'RTNCalculator' +): ?Spec); +``` + + + + +```typescript title="NativeCalculator.ts" +import type { TurboModule } from 'react-native/Libraries/TurboModule/RCTExport'; +import { TurboModuleRegistry } from 'react-native'; + +export interface Spec extends TurboModule { + add(a: number, b: number): Promise; +} + +export default TurboModuleRegistry.get( + 'RTNCalculator' +) as Spec | null; +``` + + + + +At the beginning of the spec files are the imports: + +- The `TurboModule` type, which defines the base interface for all TurboModules +- The `TurboModuleRegistry` JavaScript module, which contains functions for loading TurboModules + +The second section of the file contains the interface specification for the TurboModule. In this case, the interface defines the `add` function which takes two numbers and returns a promise that resolves to a number. This interface type **must** be named `Spec` for a TurboModule. + +Finally, we invoke `TurboModuleRegistry.get`, passing the module's name, which will load the TurboModule if it's available. + +:::caution +We are writing JavaScript files importing types from libraries, without setting up a proper node module and installing its dependencies. Your IDE will not be able to resolve the import statements and you may see errors and warnings. This is expected and will not cause problems when you add the module to your app. +::: + +## 3. Module Configuration + +Next, you need to add some configuration for [**Codegen**](pillars-codegen.md) and auto-linking. + +Some of these configuration files are shared between iOS and Android, while the others are platform-specific. + +### Shared + +The shared configuration is a `package.json` file that will be used by yarn when installing your module. Create the `package.json` file in the root of the `RTNCalculator` directory. + +```json title="package.json" +{ + "name": "rtn-calculator", + "version": "0.0.1", + "description": "Add numbers with TurboModules", + "react-native": "js/index", + "source": "js/index", + "files": [ + "js", + "android", + "ios", + "rtn-calculator.podspec", + "!android/build", + "!ios/build", + "!**/__tests__", + "!**/__fixtures__", + "!**/__mocks__" + ], + "keywords": ["react-native", "ios", "android"], + "repository": "https://github.com//rtn-calculator", + "author": " (https://github.com/)", + "license": "MIT", + "bugs": { + "url": "https://github.com//rtn-calculator/issues" + }, + "homepage": "https://github.com//rtn-calculator#readme", + "devDependencies": {}, + "peerDependencies": { + "react": "*", + "react-native": "*" + }, + "codegenConfig": { + "libraries": [ + { + "name": "RTNCalculatorSpec", + "type": "modules", + "jsSrcsDir": "js" + } + ] + } +} +``` + +The upper part of the file contains some descriptive information like the name of the component, its version and its source files. Make sure to update the various placeholders which are wrapped in `<>`: replace all the occurrences of the ``, ``, and `` tokens. + +Then there are the dependencies for this package. For this guide, you need `react` and `react-native`. + +Finally, the **Codegen** configuration is specified by the `codegenConfig` field. It contains an array of libraries, each of which is defined by three other fields: + +- `name`: The name of the library. By convention, you should add the `Spec` suffix. +- `type`: The type of module contained by this package. In this case, it is a TurboModule, thus the value to use is `modules`. +- `jsSrcsDir`: the relative path to access the `js` specification that is parsed by **Codegen**. + +### iOS: Create the `podspec` file + +For iOS, you'll need to create a `rtn-calculator.podspec` file which will define the module as a dependency for your app. It will stay in the root of `RTNCalculator`, alongside the `ios` folder. + +The file will look like this: + +```ruby title="rtn-calculator.podspec" +require "json" + +package = JSON.parse(File.read(File.join(__dir__, "package.json"))) + +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' + +Pod::Spec.new do |s| + s.name = "rtn-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" + + s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1" + s.pod_target_xcconfig = { + "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"", + "OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1", + "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 +``` + +The `.podspec` file has to be a sibling of the `package.json` file and its name is the one we set in the `package.json`'s `name` property: `rtn-calculator`. + +The first part of the file prepares some variables we will use throughout the rest of it. Then, there is a section that contains some information used to configure the pod, like its name, version, and description. Finally, we have a set of dependencies that are required by the New Architecture. + +### Android: `build.gradle`, `AndroidManifest.xml`, a `ReactPackage` class + +To prepare Android to run **Codegen** you have to create three files: + +1. The `build.gradle` with the **Codegen** configuration +1. The `AndroidManifest.xml` file +1. A java class that implements the `ReactPackage` interface. + +At the end of these steps, the `android` folder should look like this: + +```title="Android Folder Structure" +android +├── build.gradle +└── src + └── main + ├── AndroidManifest.xml + └── java + └── com + └── rtncalculator + └── RTNCalculatorPackage.java +``` + +#### The `build.gradle` file + +First, create a `build.gradle` file in the `android` folder, with the following contents: + +```kotlin title="build.gradle" +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.1.1") + } +} + +apply plugin: 'com.android.library' +apply plugin: 'com.facebook.react' + +android { + compileSdkVersion safeExtGet('compileSdkVersion', 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:+' +} + +react { + jsRootDir = file("../js/") + libraryName = "RTNCalculator" + codegenJavaPackageName = "com.RTNCalculator" +} +``` + +Of interest in the `build.gradle` file: + +- The `react` block configures the Codegen process. For Android, we need to specify: + - the `jsRootDir`, which contains the relative path to the JavaScript specs + - the `libraryName` we will use to link the library in the app. + - the `codegenJavaPackageName` which corresponds to the name of the Java package we will use for the code generated by **Codegen**. + +#### The `AndroidManifest.xml` + +Second, create an `android/src/main` folder. Inside that folder, create a `AndroidManifest.xml` file, with the following code: + +```xml title="AndroidManifest.xml" + + +``` + +This is a small manifest file that defines the package for your module. + +#### The `ReactPackage` class + +Finally, you need a class that extends the `TurboReactPackage` interface. To run the **Codegen** process, you don't have to completely implement the package class: an empty implementation is enough for the app to pick up the module as a proper React Native dependency and to try and generate the scaffolding code. + +Create an `android/src/main/java/com/rtncalculator` folder and, inside that folder, create a `RTNCalculatorPackage.java` file. + +```java title="RTNCalculatorPackage.java" +package com.RTNCalculator; + +import androidx.annotation.Nullable; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.module.model.ReactModuleInfoProvider; +import com.facebook.react.TurboReactPackage; + +import java.util.Collections; +import java.util.List; + +public class CalculatorPackage extends TurboReactPackage { + + @Nullable + @Override + public NativeModule getModule(String name, ReactApplicationContext reactContext) { + return null; + } + + @Override + public ReactModuleInfoProvider getReactModuleInfoProvider() { + return null; + } +} +``` + +The `ReactPackage` interface is used by React Native to understand what native classes the app has to use for the `ViewManager` and `Native Modules` exported by the library. + +## 4. Native Code + +For the final step in getting your TurboModule ready to go, you'll need to write some native code to connect the JavaScript side to the native platforms. This process requires two main steps: + +- Run **Codegen** to see what it generates. +- Write your native code, implementing the generated interfaces. + +When developing a React Native app that uses a TurboModule, it is responsibility of the app to actually generate the code using **Codegen**. However, when developing a TurboModule as a library, we need to reference the generated code, and it is therefore useful to see what the app will generate. + +As first step for both iOS and Android, this guide shows how to execute manually the scripts used by **Codegen** to generate the required code. Further information on **Codegen** can be found [here](pillars-codegen.md) + +:::caution +The code generated by **Codegen** in this step should not be committed to the versioning system. React Native apps are able to generate the code when the app is built. This allows an app to ensure that all libraries have code generated for the correct version of React Native. +::: + +### iOS + +#### Generate the code - iOS + +To run Codegen for the iOS platform, we need to open a terminal and run the following command: + +```sh title="Running Codegen for iOS" +cd MyApp +yarn add ../RTNCalculator +cd .. +node MyApp/node_modules/react-native/scripts/generate-artifacts.js \ + --path MyApp/ \ + --outputPath RTNCalculator/generated/ +``` + +This script first adds the `RTNCalculator` module to the app with `yarn add`. Then, it invokes Codegen via the `generate-artifacts.js` script. + +The `--path` option specifies the path to the app, while the `--outputPath` option tells Codegen where to output the generated code. + +The output of this process is the following folder structure: + +```sh +generated +└── build + └── generated + └── ios + ├── FBReactNativeSpec + │  ├── FBReactNativeSpec-generated.mm + │  └── FBReactNativeSpec.h + ├── RCTThirdPartyFabricComponentsProvider.h + ├── RCTThirdPartyFabricComponentsProvider.mm + ├── RTNCalculatorSpec + │  ├── RTNCalculatorSpec-generated.mm + │  └── RTNCalculatorSpec.h + └── react + └── renderer + └── components + └── rncore + ├── ComponentDescriptors.h + ├── EventEmitters.cpp + ├── EventEmitters.h + ├── Props.cpp + ├── Props.h + ├── RCTComponentViewHelpers.h + ├── ShadowNodes.cpp + └── ShadowNodes.h +``` + +The relevant path for the TurboModule interface is `generated/build/generated/ios/RTNCalculatorSpec`. + +See the [Codegen](./pillars-codegen) section for further details on the generated files. + +:::note +When generating the scaffolding code using **Codegen**, iOS does not clean the `build` folder automatically. If you changed a the Spec name, for example, and then run **Codegen** again, the old files will be retained. +If that happens, remember to remove the `build` folder before running the **Codegen** again. + +``` +cd MyApp/ios +rm -rf build +``` + +::: + +#### Write the Native iOS Code + +Now add the Native code for your TurboModule. Create two files in the `RTNCalculator/ios` folder: + +1. The `RTNCalculator.h`, a header file for the module. +2. The `RTNCalculator.mm`, the implementation of the module. + +##### RTNCalculator.h + +```objc title="RTNCalculator.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface RTNCalculator : NSObject + +@end + +NS_ASSUME_NONNULL_END +``` + +This file defines the interface for the `RTNCalculator` module. Here, we can add any native method we may want to invoke on the view. For this guide, we don't need anything, therefore the interface is empty. + +##### RTNCalculator.mm + +```objc title="RTNCalculator.mm" +#import "RTNCalculatorSpec.h" +#import "RTNCalculator.h" + +@implementation RTNCalculator + +RCT_EXPORT_MODULE(RTNCalculator) + +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); +} + +- (std::shared_ptr)getTurboModule: + (const facebook::react::ObjCTurboModule::InitParams &)params +{ + return std::make_shared(params); +} + +@end +``` + +The most important call is to the `RCT_EXPORT_MODULE`, which is required to export the module so that React Native can load the TurboModule. + +Then the `RCT_REMAP_METHOD` macro is used to expose the `add` method. + +Finally, the `getTurboModule` method gets an instance of the TurboModule so that the JavaScript side can invoke its methods. The function is defined in (and requested by) the `RTNCalculatorSpec.h` file that was generated earlier by Codegen. + +:::info +There are other macros that can be used to export modules and methods. You view the code that specifies them [here](https://github.com/facebook/react-native/blob/main/React/Base/RCTBridgeModule.h). +::: + +### Android + +Android follows similar steps to iOS. We have to generate the code for Android, and then we have to write some native code to make it work. + +#### Generate the Code - Android + +To generate the code for Android, we need to manually invoke Codegen. This is done similarly to what we did for iOS: first, we need to add the package to the app and then we need to invoke a script. + +```sh title="Running Codegen for Android" +cd MyApp +yarn add ../RTNCalculator +cd android +./gradlew generateCodegenArtifactsFromSchema +``` + +This script first adds the package to the app, in the same way iOS does. Then, after moving to the `android` folder, it invokes a Gradle task to create the generated code. + +:::note +To run **Codegen**, you need to enable the **New Architecture** in the Android app. This can be done by opening the `gradle.properties` files and by switching the `newArchEnabled` property from `false` to `true`. +::: + +The generated code is stored in the `MyApp/node_modules/rtn-calculator/android/build/generated/source/codegen` folder and it has this structure: + +```title="Android generated code" +codegen +├── java +│  └── com +│  └── RTNCalculator +│  └── NativeCalculatorSpec.java +├── jni +│  ├── Android.mk +│  ├── RTNCalculator-generated.cpp +│  ├── RTNCalculator.h +│  └── react +│  └── renderer +│  └── components +│  └── RTNCalculator +│  ├── ComponentDescriptors.h +│  ├── EventEmitters.cpp +│  ├── EventEmitters.h +│  ├── Props.cpp +│  ├── Props.h +│  ├── ShadowNodes.cpp +│  └── ShadowNodes.h +└── schema.json +``` + +#### Write the Native Android Code + +The native code for the Android side of a TurboModule requires: + +1. to create a `RTNCalculatorModule.java` that implements the module. +2. to update the `RTNCalculatorPackage.java` created in the previous step. + +The final structure within the Android library should look like this: + +```title="Android Folder Structure" +android +├── build.gradle +└── src + └── main + ├── AndroidManifest.xml + └── java + └── com + └── RTNCalculator + ├── CalculatorModule.java + └── CalculatorPackage.java +``` + +##### Creating the `CalculatorModule.java` + +```java title="CalculatorModule.java" +package com.RTNCalculator; + +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 String NAME = "RTNCalculator"; + + 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); + } +} +``` + +This class implements the module itself, which extends the `NativeCalculatorSpec` that was generated from the `NativeCalculator` JavaScript specification file. + +##### Updating the `CalculatorPackage.java` + +```diff title="CalculatorPackage.java" +package com.RTNCalculator; + +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 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 null; ++ return () -> { ++ final Map moduleInfos = new HashMap<>(); ++ moduleInfos.put( ++ CalculatorModule.NAME, ++ new ReactModuleInfo( ++ CalculatorModule.NAME, ++ CalculatorModule.NAME, ++ false, // canOverrideExistingModule ++ false, // needsEagerInit ++ true, // hasConstants ++ false, // isCxxModule ++ true // isTurboModule ++ )); ++ return moduleInfos; ++ }; + } +} +``` + +This is the last piece of Native Code for Android. It defines the `TurboReactPackage` object that will be used by the app to load the module. + +## 5. Adding the TurboModule to your App + +Now you can install and use the TurboModule in your app. + +### Shared + +First of all, we need to add the NPM package which contains the Component to the app. This can be done with the following command: + +```sh +cd MyApp +yarn add ../RTNCalculator +``` + +This command will add the `RTNCalculator` module to the `node_modules` of your app. + +### iOS + +Then, you need to install the new dependencies in your iOS project. To do so, run these commands: + +```sh +cd ios +RCT_NEW_ARCH_ENABLED=1 bundle exec pod install +``` + +This command will look for all the dependencies of the project and it will install the iOS ones. The `RCT_NEW_ARCH_ENABLED=1` instruct **Cocoapods** that it has to run some additional operations to run **Codegen**. + +:::note +You may have to run `bundle install` once before you can use `RCT_NEW_ARCH_ENABLED=1 bundle exec pod install`. You won't need to run `bundle install` anymore, unless you need to change the Ruby dependencies. +::: + +### Android + +Android configuration requires slightly more steps in order to be able to use your new TurboModule. + +First, to enable the **New Architecture**: + +1. Open the `android/gradle.properties` file +2. Scroll down to the end of the file and switch the `newArchEnabled` property from `false` to `true`. + +Then, to manually link your new TurboModule: + +1. Open the `NewArchitecture/android/app/src/main/jni/Android.mk` file and update the file as it follows: + ```diff + # 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 + + + + include $(NODE_MODULES_DIR)/rtn-calculator/android/build/generated/source/codegen/jni/Android.mk + include $(CLEAR_VARS) + ``` +1. In the same file above, go to the `LOCAL_SHARED_LIBRARIES` setting and add the following line: + ```diff + libreact_codegen_rncore \ + + libreact_codegen_RTNCalculator \ + libreact_debug \ + ``` +1. Open the `NewArchitecture/android/app/src/main/jni/MainApplicationModuleProvider.cpp` file and update the file as it follows: + + 1. Add the import for the calculator: + ```diff + #include + + #include + ``` + 1. Add the following check in the `MainApplicationModuleProvider` constructor: + + ```diff + // auto module = samplelibrary_ModuleProvider(moduleName, params); + // if (module != nullptr) { + // return module; + // } + + + auto module = RTNCalculator_ModuleProvider(moduleName, params); + + if (module != nullptr) { + + return module; + + } + + return rncore_ModuleProvider(moduleName, params); + } + ``` + +### JavaScript + +Now you can use your TurboModule calculator in your app! + +Here's an example App.js file using the `add` method: + +```js title="App.js" +/** + * 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 RTNCalculator from 'rtn-calculator/js/NativeCalculator.js'; + +const App: () => Node = () => { + const [result, setResult] = useState(null); + return ( + + + + 3+7={result ?? '??'} + +