From 3e229de4468d5dd0c07c60393811278c3c77c482 Mon Sep 17 00:00:00 2001 From: Riccardo Cipolleschi Date: Mon, 23 May 2022 16:45:05 +0200 Subject: [PATCH 01/32] [Feat] Add intro for Fabric Components --- .../pillars-fabric-components.md | 38 ++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/docs/the-new-architecture/pillars-fabric-components.md b/docs/the-new-architecture/pillars-fabric-components.md index 72892522d16..5b27015d351 100644 --- a/docs/the-new-architecture/pillars-fabric-components.md +++ b/docs/the-new-architecture/pillars-fabric-components.md @@ -3,17 +3,37 @@ id: pillars-fabric-components title: Fabric Components --- -This section contains a high-level introduction to Fabric components. It provides enough context to understand when a Fabric component is needed and how it roughly works. -It points to the [Renderer](https://reactnative.dev/architecture/fabric-renderer) section of the [Architecture](https://reactnative.dev/architecture/overview) tab for a deep dive into the technical details. +A Fabric Component is a UI component rendered on the screen using the [Fabric Renderer](https://reactnative.dev/architecture/fabric-renderer). -This section must have a warning that it works only with the new architecture enabled. It points to the [migration section](../new-architecture-intro). +Using Fabric Components instead of Native Components allows us to reap all the [benefits](./why) of the **New Architecture**. Specifically, we are able to leverage JSI to efficiently connect the Native UI code JavaScript. + +A Fabric Component is created starting from a **JavaScript specification**. This, with the help of [**CodeGen**](./pillars-codegen), will create some C++ code, integrated in the platform native layer and shared among all the React Native platforms. The C++ code is boilerplate code that the component-specific logic needs to use to be properly used by React Native. After the component-specific logic has been connected with the generated code, the component can be integrated in the app. + +The following section will guide you through the creation of a Fabric Component, step-by-step. + +:::caution +Fabric Components only works with the **New Architecture** enabled. +To migrate to the **New Architecture**, follow the [Migration guide](../new-architecture-intro) +::: ## How to Create a Fabric Components -This section is a step-by-step guide to create a Fabric component from scratch. The list of subsections is roughly: +To create a Fabric Component, we have to follow these steps: + +- Define a set of JavaScript specifications. +- Configure the component so that it can be consumed by an app. +- Write the native code required to make it work. + +Once these steps are done, the component is ready to be consumed by an app. Therefore, the guide shows how to add it to an app, leveraging _autolinking_, and how to reference it from the JavaScript code. + +### Javascript Specification + +### Component Configuration + +### Native Code + +#### iOS + +#### Android -- 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) +### Adding the Fabric Component To Your App From 94ff5bfa15201e421755039ad9f98d3a58633048 Mon Sep 17 00:00:00 2001 From: Riccardo Date: Mon, 27 Jun 2022 16:15:41 +0100 Subject: [PATCH 02/32] feat: add guide to create a Fabric Component --- .../pillars-fabric-components.md | 846 +++++++++++++++++- docs/the-new-architecture/pillars.md | 2 +- website/core/TabsConstants.js | 9 +- website/package.json | 2 +- 4 files changed, 843 insertions(+), 16 deletions(-) diff --git a/docs/the-new-architecture/pillars-fabric-components.md b/docs/the-new-architecture/pillars-fabric-components.md index 5b27015d351..a6e1d204bc4 100644 --- a/docs/the-new-architecture/pillars-fabric-components.md +++ b/docs/the-new-architecture/pillars-fabric-components.md @@ -3,13 +3,15 @@ id: pillars-fabric-components title: Fabric Components --- +import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import constants from '@site/core/TabsConstants'; + A Fabric Component is a UI component rendered on the screen using the [Fabric Renderer](https://reactnative.dev/architecture/fabric-renderer). -Using Fabric Components instead of Native Components allows us to reap all the [benefits](./why) of the **New Architecture**. Specifically, we are able to leverage JSI to efficiently connect the Native UI code JavaScript. +Using Fabric Components instead of Native Components allows us to reap all the [benefits](./why) of the **New Architecture**. Specifically, it enables to use JSI to efficiently connect the Native UI code with JavaScript. -A Fabric Component is created starting from a **JavaScript specification**. This, with the help of [**CodeGen**](./pillars-codegen), will create some C++ code, integrated in the platform native layer and shared among all the React Native platforms. The C++ code is boilerplate code that the component-specific logic needs to use to be properly used by React Native. After the component-specific logic has been connected with the generated code, the component can be integrated in the app. +A Fabric Component is created starting from a **JavaScript specification**. Then [**CodeGen**](./pillars-codegen) creates some C++ code, integrated in the platform native layer and shared among all the React Native platforms. This C++ code is boilerplate code that component-specific logic needs to import and use to be properly adopted by React Native. After the component-specific logic is connected with the generated code, the component can be integrated in the app. -The following section will guide you through the creation of a Fabric Component, step-by-step. +The following section guides you through the creation of a Fabric Component, step-by-step. :::caution Fabric Components only works with the **New Architecture** enabled. @@ -18,22 +20,840 @@ To migrate to the **New Architecture**, follow the [Migration guide](../new-arch ## How to Create a Fabric Components -To create a Fabric Component, we have to follow these steps: +To create a Fabric Component, you have to follow these steps: + +1. Define a set of JavaScript specifications. +2. Configure the component so that **CodeGen** can create the shared code and it can added as a dependency for an app. +3. Write the required native code required. + +Once these steps are done, the component is ready to be consumed by an app. The guide shows how to add it to an app, leveraging _autolinking_, and how to reference it from the JavaScript code. + +## 1. Folder Setup + +In order to keep the component 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 Fabric Component that can be released as open-source libraries later. + +For this guide, you are going to create a Fabric Component that centers some text on the screen. + +Let's create a new folder at the same level of the app and let's call it `RTNCenteredText`. + +In this folder, let's create three subfolders: `js`, `ios` and `android`. + +The final result should look like this: + +```sh +. +├── MyApp +└── RTNCenteredText + ├── 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** uses 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 `NativeComponent`, with a `.js` or `.jsx` extension when using Flow, or a `.ts`, or `.tsx` extension when using TypeScript. **CodeGen** only looks for files matching this pattern. +2. The file must export a `HostComponent` object. + +The following are the specification of our `RTNCenteredText` component in both Flow and TypeScript: let's create a `RTNCenteredText` file with the proper extension in the `js` folder. + + + + +```typescript +// @flow strict-local + +import type {ViewProps} from 'react-native/Libraries/Components/View/ViewPropTypes'; +import type {HostComponent} from 'react-native'; +import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent'; + +type NativeProps = $ReadOnly<{| + ...ViewProps, + text: ?string, + // add other props here +|}>; + +export default (codegenNativeComponent( + 'RTNCenteredText', +): HostComponent); +``` + + + + +```typescript +import type { ViewProps } from 'ViewPropTypes'; +import type { HostComponent } from 'react-native'; +import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent'; + +export interface NativeProps extends ViewProps { + ...ViewProps, + text: string | null | undefined, + // add other props here +} + +export default codegenNativeComponent( + 'RTNCenteredText' +) as HostComponent; +``` + + + + +Let's break these down a little. + +At the beginning of the spec files, there are the imports. The most important imports, required by every Fabric Component, are: + +- The `HostComponent`: type our exported component needs to conform to. +- The `codegenNativeComponent` function: responsible to actually register the component in the JavaScript runtime. + +The second section of the files contains the **props** of the component. [Props](https://reactnative.dev/docs/next/intro-react#props) (short for "properties") are component-specific information that let you customize React components. In this case, you want to control the `text` property of the component. + +Finally, the spec file exports the returned value of the `codegenNativeComponent` generic function, invoked passing the name of the component. + +:::caution +The JavaScript files imports types from libraries, without setting up a proper node module and installing its dependencies. The outcome of this is that the IDE may have troubles resolving the import statements and it can output errors and warnings. +These will disappear as soon as the Fabric Component is added as a dependency of a React Native app. +::: + +## 3. Component Configuration + +The second element needed to properly develop a Fabric Component is a bit of configuration, that helps setting up: + +- all the data the **CodeGen** process requires to run properly +- the files required to link the Fabric Component into the app + +Some of these configuration are shared between iOS and Android, while the others are platform-specific. + +### Shared + +The shared configuration is a `package.json` file that is used by yarn when installing your component. Create the `package.json` file in the root of the `RTNCenteredText` directory. + +```json title="package.json" +{ + "name": "rnt-centered-text", + "version": "0.0.1", + "description": "Showcase a Fabric Component with a centered text", + "react-native": "js/index", + "source": "js/index", + "files": [ + "js", + "android", + "ios", + "rnt-centered-text.podspec", + "!android/build", + "!ios/build", + "!**/__tests__", + "!**/__fixtures__", + "!**/__mocks__" + ], + "keywords": ["react-native", "ios", "android"], + "repository": "https://github.com//rnt-centered-text", + "author": " (https://github.com/)", + "license": "MIT", + "bugs": { + "url": "https://github.com//rnt-centered-text/issues" + }, + "homepage": "https://github.com//rnt-centered-text#readme", + "devDependencies": {}, + "peerDependencies": { + "react": "*", + "react-native": "*" + }, + "codegenConfig": { + "libraries": [ + { + "name": "RTNCenteredTextSpecs", + "type": "components", + "jsSrcsDir": "js" + } + ] + } +} +``` + +The upper part of the file contains some descriptive information like the name of the component, its version and what composes it. 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, specifically you need `react` and `react-native`, and you can add all the other dependencies you may have. + +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 `Specs` suffix. +- `type`: The type of module contained by this package. In this case, it is a component, thus the value to use is `components`. +- `jsSrcsDir`: the relative path to access the `js` specification that is parsed by the **CodeGen**. + +### iOS: Create the `podspec` file + +Now, let's configure the native component to generate the required code to integrate it with an app. For further information on how the **CodeGen**, have a look [here](./pillars-codegen.md). + +For iOS, you need to create a `.podspec` file which defines the component as a dependency. This file stays in the root folder of the `RTNCenteredText` component, _outside_ the `ios` folder. +The `.podspec` file for the component looks like this + +```ruby title="rnt-centered-text.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 = "rnt-centered-text" + 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-RCTFabric" + 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 set in the `package.json`'s `name` property: `rnt-centered-text`. + +The first part of the file prepares some variables used throughout the rest of it. Then, there is a section that contains some information that configures the pod, like its name, version, and description. Finally, there is a set of dependencies that are required by the New Architecture. + +### Android: Create the `build.gradle` file + +You need to 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) + + defaultConfig { + minSdkVersion safeExtGet('minSdkVersion', 21) + targetSdkVersion safeExtGet('targetSdkVersion', 31) + buildConfigField("boolean", "IS_NEW_ARCHITECTURE_ENABLED", "true") + } +} + +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 = "RTNCenteredText" + codegenJavaPackageName = "com.RTNCenteredText" +} +``` + +The `build.gradle` file contains various section to properly configure the library and to access the required dependencies. The most interesting sections are: + +- the `defaultConfig` block within the `android` block, where a `buildConfigField` is added to enable the new architecture. +- the `react` block where you configure the **CodeGen** process. Here, the following properties are required: + - The `jsRootDir` contains the relative path to the JavaScript specs. + - The `libraryName` is used to link the library to the app. + - The `codegenJavaPackageName` that corresponds to the name of the Java Package that is used to group the code generated by **CodeGen**. + +## 4. Native Code + +The last step requires you to write some native code to connect the JavaScript side of the Component to what is offered by the platforms. This process requires two main steps: + +1. Run the **CodeGen** to see what would be generated. +2. Write the native code that will make it work. + +When developing a React Native app that uses a Fabric Component, it is responsibility of the app to actually generate the code using **CodeGen**. However, when developing a Fabric Component as a library, it needs to reference the generated code and it is 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 the **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 for all the libraries. This ensure that a consistent version of the **CodeGen** is used across all the libraries. +::: + +### iOS + +#### Generate the code - iOS + +To run Codegen for the iOS platform, open a terminal and run the following command: + +```sh +cd MyApp +yarn add ../RTNCenteredText +cd .. +node MyApp/node_modules/react-native/scripts/generate-artifacts.js \ + --path MyApp/ \ + --outputPath RTNCenteredText/generated/ +``` + +This script first adds the `RTNCenteredText` 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 the script 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 + └── react + └── renderer + └── components + ├── RTNCenteredTextSpecs + │ ├── ComponentDescriptors.h + │ ├── EventEmitters.cpp + │ ├── EventEmitters.h + │ ├── Props.cpp + │ ├── Props.h + │ ├── RCTComponentViewHelpers.h + │ ├── ShadowNodes.cpp + │ └── ShadowNodes.h + └── rncore + ├── ComponentDescriptors.h + ├── EventEmitters.cpp + ├── EventEmitters.h + ├── Props.cpp + ├── Props.h + ├── RCTComponentViewHelpers.h + ├── ShadowNodes.cpp + └── ShadowNodes.h +``` + +The relevant path for the component is `generated/build/generated/ios/react/renderer/components/RTNCenteredTextSpecs`. +This folder contains all the generated code required by our Component. + +See the [CodeGen](./pillars-codegen) section for further details on the generated files. + +#### Write the Native iOS Code + +Now that the scaffolding code has been generated, it's time to write the Native code for our Fabric Component. +You need to create three files in the `RTNCenteredText/ios` folder: + +1. The `RTNCenteredTextManager.mm`, an Objective-C++ file that declares what the Component exports. +2. The `RTNCenteredText.h`, a header file for the actual view. +3. The `RTNCenteredText.mm`, the implementation of the view. + +##### RTNCenteredTextManager.mm + +```objc title="RTNCenteredTextManager.mm" +#import +#import +#import + +@interface RTNCenteredTextManager : RCTViewManager +@end + +@implementation RTNCenteredTextManager + +RCT_EXPORT_MODULE(RTNCenteredText) + +RCT_EXPORT_VIEW_PROPERTY(text, NSString) + +@end +``` + +This file is the manager for the Fabric Component. + +The most important call is to the `RCT_EXPORT_MODULE` which is required to export the module so that Fabric can retrieve and instantiate it. + +Then, you have to expose the `text` property for the Fabric Component. This is done with the `RCT_EXPORT_VIEW_PROPERTY` macro, specifying a name and a type. + +:::info +There are other macros that can be used to export custom properties, emitters and other constructs. You can view the code that specifies them [here](https://github.com/facebook/react-native/blob/main/React/Views/RCTViewManager.h) +::: + +##### RTNCenteredText.h + +```objc title="RTNCenteredText.h" +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface RTNCenteredText : RCTViewComponentView + +@end + +NS_ASSUME_NONNULL_END +``` + +This file defines the interface for the `RTNCenteredText` view. Here, you can add any native method you may want to invoke on the view. For this guide, you don't need anything, therefore the interface is empty. + +##### RTNCenteredText.mm + +```cpp title="RTNCenteredText.mm" +#import "RTNCenteredText.h" + +#import +#import +#import +#import -- Define a set of JavaScript specifications. -- Configure the component so that it can be consumed by an app. -- Write the native code required to make it work. +#import "RCTFabricComponentsPlugins.h" -Once these steps are done, the component is ready to be consumed by an app. Therefore, the guide shows how to add it to an app, leveraging _autolinking_, and how to reference it from the JavaScript code. +using namespace facebook::react; -### Javascript Specification +@interface RTNCenteredText () +@end -### Component Configuration +@implementation RTNCenteredText { + UIView *_view; + UILabel *_label; +} -### Native Code ++ (ComponentDescriptorProvider)componentDescriptorProvider +{ + return concreteComponentDescriptorProvider(); +} -#### iOS +- (instancetype)initWithFrame:(CGRect)frame +{ + if (self = [super initWithFrame:frame]) { + static const auto defaultProps = std::make_shared(); + _props = defaultProps; + + _view = [[UIView alloc] init]; + _view.backgroundColor = [UIColor redColor]; + + _label = [[UILabel alloc] init]; + _label.text = @"Initial value"; + [_view addSubview:_label]; + + _label.translatesAutoresizingMaskIntoConstraints = false; + [NSLayoutConstraint activateConstraints:@[ + [_label.leadingAnchor constraintEqualToAnchor:_view.leadingAnchor], + [_label.topAnchor constraintEqualToAnchor:_view.topAnchor], + [_label.trailingAnchor constraintEqualToAnchor:_view.trailingAnchor], + [_label.bottomAnchor constraintEqualToAnchor:_view.bottomAnchor], + ]]; + + _label.textAlignment = NSTextAlignmentCenter; + + self.contentView = _view; + } + + return self; +} + +- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps +{ + const auto &oldViewProps = *std::static_pointer_cast(_props); + const auto &newViewProps = *std::static_pointer_cast(props); + + if (oldViewProps.text != newViewProps.text) { + _label.text = [[NSString alloc] initWithCString:newViewProps.text.c_str() encoding:NSASCIIStringEncoding]; + } + + [super updateProps:props oldProps:oldProps]; +} + +@end + +Class RTNCenteredTextCls(void) +{ + return RTNCenteredText.class; +} +``` + +This file contains the actual implementation of the view. + +It starts with some imports which require you to read the files generated by the **CodeGen**. + +The component has to conform to a specific protocol generated by the **CodeGen**, in this case `RCTRTNCenteredTextViewProtocol`. + +Then, the file defines a static `(ComponentDescriptorProvider)componentDescriptorProvider` method which is used by Fabric to retrieve the descriptor provider to instantiate the object. + +Then, there is the constructor of the view: the `init` method. In this method, it is important to create a `defaultProps` struct using the `RTNCenteredTextProps` type from the **CodeGen**. You need to assign it to the private `_props` property to correctly initialize the Fabric Component. The remaining part of the initializer is standard Objective-C code to create views and layout them with AutoLayout. + +The last two pieces are the `updateProps` method and the `RTNCenteredTextCls` method. + +The `updateProps` method is invoked by Fabric every time a prop changes in JavaScript. The props passed as parameters are downcasted to the proper `RTNCenteredTextProps` type and then they are used to update the native code if needed. Notice that the superclass method `[super updateProps]` must be invoked as the last statement of this method, otherwise the `props` and `oldProps` struct will have the same values and you'll not be able to use them to make decisions and to update the component. + +Finally, the `RTNCenteredTextCls` is another static method used to retrieve the correct instance of the class at runtime. + +:::caution +Differently from Native Components, Fabric requires to manually implement the `updateProps` method. It's not enough to export properties with the `RCT_EXPORT_XXX` and `RCT_REMAP_XXX` macros. +::: + +### Android + +Android follows some similar steps to iOS. You have to generate the code, and then you have to write some native code to make it works. + +#### Generate the Code - Android + +To generate the code, you need to manually invoke the **CodeGen**. This is done similarly to what you need to do for iOS: first, you need to add the package to the app and then you need to invoke a script. + +```sh title="Running CodeGen for Android" +cd MyApp +yarn add ../RTNCenteredText +cd android +./gradlew generateCodegenArtifactsFromSchema --rerun-tasks +``` + +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 codegen. + +:::note +To run the **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/rnt-centered-text/android/build/generated/source/codegen` folder and it has this structure: + +```title="Android generated code" +codegen +├── java +│ └── com +│ └── facebook +│ └── react +│ └── viewmanagers +│ ├── RTNCenteredTextManagerDelegate.java +│ └── RTNCenteredTextManagerInterface.java +├── jni +│ ├── Android.mk +│ ├── CMakeLists.txt +│ ├── RTNCenteredText-generated.cpp +│ ├── RTNCenteredText.h +│ └── react +│ └── renderer +│ └── components +│ └── RTNCenteredText +│ ├── ComponentDescriptors.h +│ ├── EventEmitters.cpp +│ ├── EventEmitters.h +│ ├── Props.cpp +│ ├── Props.h +│ ├── ShadowNodes.cpp +│ └── ShadowNodes.h +└── schema.json +``` + +You can see that the content of the `codegen/jni/react/renderer/components/RTNCenteredTextSpecs` looks similar to the files created by the iOS counterpart. Other interesting pieces are the `Android.mk` and `CMakeList.txt` files, which you need to configure the Fabric Component in the app. And the `RTNCenteredTextManagerDelegate.java` and `RTNCenteredTextManagerInterface.java` that you need to use in our manager. + +See the [CodeGen](./pillars-codegen) section for further details on the generated files. + +#### Write the Native Android Code + +The native code for the Android side of a Fabric Components requires four pieces: + +1. An `AndroidManifest.xml` file. +2. A `RTNCenteredText.java` that represents the actual view. +3. A `RTNCenteredTextManager.java` to instantiate the view. +4. A `RTNCenteredTextPackage.java` that React Native uses to configure the library. + +The final structure within the Android library should be like this. + +```title="Android Folder Structure" +android +├── build.gradle +└── src + └── main + ├── AndroidManifest.xml + └── java + └── com + └── rtncenteredtext + ├── RTNCenteredText.java + ├── RTNCenteredTextManager.java + └── RTNCenteredTextPackage.java +``` + +##### AndroidManifest.xml + +```xml title="AndroidManifest.xml" + + +``` + +This is a small manifest file that defines the package for our module. + +##### RTNCenteredText.java + +```java title="RTNCenteredText" +package com.rtncenteredtext; + +import androidx.annotation.Nullable; +import android.content.Context; +import android.util.AttributeSet; +import android.graphics.Color; + +import android.widget.TextView; +import android.view.Gravity; + +public class RTNCenteredText extends TextView { + + public RTNCenteredText(Context context) { + super(context); + this.configureComponent(); + } + + public RTNCenteredText(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + this.configureComponent(); + } + + public RTNCenteredText(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + this.configureComponent(); + } + + private void configureComponent() { + this.setBackgroundColor(Color.RED); + this.setGravity(Gravity.CENTER_HORIZONTAL); + } +} +``` + +This class represents the actual view Android is going to represent on screen. It inherit from `TextView` and it configures the basic aspects of itself using a private `configureComponent()` function + +##### RTNCenteredTextManager.java + +```java title="RTNCenteredTextManager.java" +package com.rtncenteredtext; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.module.annotations.ReactModule; +import com.facebook.react.uimanager.SimpleViewManager; +import com.facebook.react.uimanager.ThemedReactContext; +import com.facebook.react.uimanager.ViewManagerDelegate; +import com.facebook.react.uimanager.annotations.ReactProp; +import com.facebook.react.viewmanagers.RTNCenteredTextManagerInterface; +import com.facebook.react.viewmanagers.RTNCenteredTextManagerDelegate; + + +@ReactModule(name = RTNCenteredTextManager.NAME) +public class RTNCenteredTextManager extends SimpleViewManager + implements RTNCenteredTextManagerInterface { + + private final ViewManagerDelegate mDelegate; + + static final String NAME = "RTNCenteredText"; + + public RTNCenteredTextManager(ReactApplicationContext context) { + mDelegate = new RTNCenteredTextManagerDelegate<>(this); + } + + @Nullable + @Override + protected ViewManagerDelegate getDelegate() { + return mDelegate; + } + + @NonNull + @Override + public String getName() { + return RTNCenteredTextManager.NAME; + } + + @NonNull + @Override + protected RTNCenteredText createViewInstance(@NonNull ThemedReactContext context) { + return new RTNCenteredText(context); + } + + @Override + @ReactProp(name = "text") + public void setText(RTNCenteredText view, @Nullable String text) { + view.setText(text); + } +} +``` + +The `RTNCenteredTextManager` is a class used by React Native to instantiate the native component. It is the class that leverage the **CodeGen** to implement all the proper interfaces (See the `RTNCenteredTextManagerInterface` interface in the `implements` clause) and it uses the `RTNCenteredTextManagerDelegate`. + +It is also responsible to export all the constructs required by ReactNative: the class itself is annotated with `@ReactModule` and the `setText` method is annothated with `@ReactProp`. + +##### RTNCenteredTextPackage.java + +```java title="RTNCenteredTextPackage" +package com.rtncenteredtext; + +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 RTNCenteredTextPackage implements ReactPackage { + + @Override + public List createViewManagers(ReactApplicationContext reactContext) { + List viewManagers = new ArrayList<>(); + viewManagers.add(new RTNCenteredTextManager(reactContext)); + return viewManagers; + } + + @Override + public List createNativeModules(ReactApplicationContext reactContext) { + return Collections.emptyList(); + } + +} +``` + +This is the last piece of Native Code for Android. It defines the Package object that is used by the app to load the manager. + +## 5. Adding the Fabric Component To Your App + +This is the last step to finally see our Fabric Component running on our app. + +### Shared + +First of all, you 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 ../RTNCenteredText +``` + +This command adds the `RTNCenteredText` Component to the `node_modules` of your app. + +### iOS + +Then, you need to install the new dependencies in our iOS project. To do so, you need to run these commands: + +```sh +cd ios +RCT_NEW_ARCH_ENABLED=1 bundle exec pod install +``` + +This command looks for all the dependencies of the project and it installs the iOS ones. The `RCT_NEW_ARCH_ENABLED=1` flag instructs **Cocoapods** that it has to execute some additional operations to run the **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 -### Adding the Fabric Component To Your App +Android configuration requires more steps to use our new Component. + +First, you need to enable the **New Architecture**. This can be done by: + +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, you need to instruct the `Android.mk` file that it needs to build also the new library. +This can be with these steps: + +1. Open the `android/app/src/main/jni/Android.mk` file +1. Add this line to include the library at the beginning of the file: + + ```diff + include $(REACT_ANDROID_DIR)/Android-prebuilt.mk + + # 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)/rnt-centered-text/android/build/generated/source/codegen/jni/Android.mk + include $(CLEAR_VARS) + ``` + +1. In the same file, scroll down until you find a list of `libreact` libraries. There, you have to add the the library that has been generated. To do so, you need to add this line: + ```diff + libreact_codegen_rncore \ + +libreact_codegen_RTNCenteredText \ + libreact_debug \ + ``` + +:::note +The name of the library is `libreact_codegen_` where `` is the value that has been set in the config. +Also, this step won't be necessary anymore as soon as a version of React Native that supports autolinking for Android is released. +::: + +Finally, you need to configure the Fabric component registry to load the Fabric Component at runtime. This can be done by: + +1. Open the `MyApp/android/app/src/main/jni/MainComponentsRegistry.cpp` +1. Add the following include: + + ```c++ + #include + ``` + +1. Update the `sharedProviderRegistry` with this line: + + ```diff + auto providerRegistry = CoreComponentsRegistry::sharedProviderRegistry(); + + +providerRegistry->add(concreteComponentDescriptorProvider()); + + // Custom Fabric Components go here. You can register custom + ``` + +### JavaScript + +Finally, you can read the Component in our JavaScript application. +To do so, you have to: + +1. Import the Component in the js file that uses it. So, if you want to use it in the `App.js`, you need to add this line: + + ```js title="App.js" + import RTNCenteredText from 'rnt-centered-text/js/RTNCenteredTextNativeComponent'; + ``` + +2. Then, you need to use it in another React Native component. The syntax is the same as for any other component: + ```js title="App.js" + // ... other code + const App: () => Node = () => { + // ... other App code ... + return ( + // ...other React Native elements... + + // ...other React Native Elements + ); + }; + ``` + +Now, you can run the React Native app and see our Component on the screen. diff --git a/docs/the-new-architecture/pillars.md b/docs/the-new-architecture/pillars.md index e390b16c47e..25ab983e22e 100644 --- a/docs/the-new-architecture/pillars.md +++ b/docs/the-new-architecture/pillars.md @@ -25,5 +25,5 @@ Finally, we dive a little deeper into the [CodeGen](pillars-codegen) process tha To integrate a TurboModule or a Fabric Component in an app, the app has to run with the New Architecture enabled. To create a new app adopting the New Architecture, refer to the [Using the App Template](use-app-template) section. -To migrate an existing app to the New Architecture, refer to the [Migration](/docs/new-architecture-intro) guide. +To migrate an existing app to the New Architecture, refer to the [Migration](../new-architecture-intro) guide. ::: diff --git a/website/core/TabsConstants.js b/website/core/TabsConstants.js index f94d13b8ed7..7068b51d316 100644 --- a/website/core/TabsConstants.js +++ b/website/core/TabsConstants.js @@ -50,6 +50,13 @@ const oses = [ ]; const defaultOs = isMacOS ? 'macos' : isWindows ? 'windows' : 'linux'; +const javaScriptSpecLanguages = [ + {label: 'Flow', value: 'flow'}, + {label: 'TypeScript', value: 'typescript'}, +]; + +const defaultJavaScriptSpecLanguages = 'flow'; + const getDevNotesTabs = (tabs = ['android', 'ios', 'web', 'windows']) => [ tabs.includes('android') ? {label: 'Android', value: 'android'} : undefined, @@ -65,7 +72,6 @@ export default { defaultPlatform, defaultSyntax, defaultAndroidLanguage, - javaScriptSpecLanguages, defaultJavaScriptSpecLanguages, getDevNotesTabs, guides, @@ -74,4 +80,5 @@ export default { platforms, syntax, androidLanguages, + javaScriptSpecLanguages, }; diff --git a/website/package.json b/website/package.json index 99980232a14..ccb3d6f4a11 100644 --- a/website/package.json +++ b/website/package.json @@ -15,7 +15,7 @@ "swizzle": "docusaurus swizzle", "deploy": "docusaurus deploy", "serve": "docusaurus serve", - "clean": "docusaurus clean", + "clean": "docusaurus clear", "test": "yarn build", "version:cut": "docusaurus docs:version", "format:source": "prettier --write {{core,src}/**/*.js,*.js}", From 00f2ba5e10870b5cf8351edb1580a7b432180519 Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Mon, 20 Jun 2022 14:16:15 -0700 Subject: [PATCH 03/32] Beginning of guide/folder structure --- .../pillars-turbomodule.md | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/docs/the-new-architecture/pillars-turbomodule.md b/docs/the-new-architecture/pillars-turbomodule.md index ee305ee1656..311a34859e6 100644 --- a/docs/the-new-architecture/pillars-turbomodule.md +++ b/docs/the-new-architecture/pillars-turbomodule.md @@ -11,8 +11,29 @@ This section must have a warning that it works only with the new architecture en This section is a step-by-step guide to create a TurboModule from scratch. The list of subsections is roughly: -- 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) +## How to Create a TurboModule + +To create a TurboModule, we need to: + +1. Define a set of JavaScript specifications. +2. Configure the module and inspect the code created by Codegen. +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. + +Next to your application, create a folder called `RTNCalculator`. (**RTN** stands for "**R**eact**T** **N**ative", and is a standard 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 +``` \ No newline at end of file From 7ab79beb8b551d5ecca8d8ed32ede9c47e787b0a Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Mon, 20 Jun 2022 14:16:18 -0700 Subject: [PATCH 04/32] WIP JS Spec --- .../pillars-turbomodule.md | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/docs/the-new-architecture/pillars-turbomodule.md b/docs/the-new-architecture/pillars-turbomodule.md index 311a34859e6..12f2d262b52 100644 --- a/docs/the-new-architecture/pillars-turbomodule.md +++ b/docs/the-new-architecture/pillars-turbomodule.md @@ -36,4 +36,28 @@ TurboModulesGuide ├── android ├── ios └── js -``` \ No newline at end of file +``` + +## 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 +``` + + + + +```typescript +``` + + + \ No newline at end of file From 221bb9f78818bd3784fe35ac0f7f6c1327734372 Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Mon, 20 Jun 2022 14:16:23 -0700 Subject: [PATCH 05/32] specification section --- .../pillars-turbomodule.md | 59 ++++++++++++++++--- 1 file changed, 52 insertions(+), 7 deletions(-) diff --git a/docs/the-new-architecture/pillars-turbomodule.md b/docs/the-new-architecture/pillars-turbomodule.md index 12f2d262b52..e581a8c4da3 100644 --- a/docs/the-new-architecture/pillars-turbomodule.md +++ b/docs/the-new-architecture/pillars-turbomodule.md @@ -3,13 +3,23 @@ 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, 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 create shared C++ code for use 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 + +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 @@ -50,14 +60,49 @@ There are two requirements the file containing this specification must meet: -```typescript +```typescript title="NativeCalculator.js" +// @flow +import type { TurboModule } from 'react-native/Libraries/TurboModule/RCTExport'; +import { TurboModuleRegistry } from 'react-native'; + +export interface RTNCalculatorSpec extends TurboModule { + add(a: number, b: number): Promise; +} +export default (TurboModuleRegistry.get( + 'RTNCalculator' +): ?RTNCalculatorSpec); ``` -```typescript +```typescript title="NativeCalculator.ts" +import type { TurboModule } from 'react-native/Libraries/TurboModule/RCTExport'; +import { TurboModuleRegistry } from 'react-native'; + +export interface RTNCalculatorSpec extends TurboModule { + add(a: number, b: number): Promise; +} + +export default (TurboModuleRegistry.get( + 'RTNCalculator' +) as RTNCalculatorSpec | null); ``` - \ No newline at end of file + + +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. + +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. +::: + +```json From 681992bab3a1d80ebf70f378e4241c02c5f2a426 Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Mon, 20 Jun 2022 14:16:29 -0700 Subject: [PATCH 06/32] Configuration --- .../pillars-turbomodule.md | 164 +++++++++++++++++- 1 file changed, 163 insertions(+), 1 deletion(-) diff --git a/docs/the-new-architecture/pillars-turbomodule.md b/docs/the-new-architecture/pillars-turbomodule.md index e581a8c4da3..e005d721472 100644 --- a/docs/the-new-architecture/pillars-turbomodule.md +++ b/docs/the-new-architecture/pillars-turbomodule.md @@ -105,4 +105,166 @@ Finally, we invoke `TurboModuleRegistry.get`, passing the module's name, which w 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. ::: -```json +## 3. Module Configuration + +Next, you need to add some configuration for [**Codegen**](/docs/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" + } + ] + } +} +``` + +### iOS: Create the `podspec` file + +For iOS, you'll need to create a `.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 `.podspec` file for the module will look like this: + +```ruby title="rnt-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: Create the `build.gradle` file + +Next, 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) + + defaultConfig { + minSdkVersion safeExtGet('minSdkVersion', 21) + targetSdkVersion safeExtGet('targetSdkVersion', 31) + buildConfigField("boolean", "IS_NEW_ARCHITECTURE_ENABLED", "true") + } +} + +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 `defaultConfig` block, within the `android` block, adds a `buildConfigField` to enable the New Architecture. +- 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**. From a5d4194b47b946d03265e7c5a9e7e51fc1edcbb7 Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Mon, 20 Jun 2022 14:16:40 -0700 Subject: [PATCH 07/32] native code intro --- docs/the-new-architecture/pillars-turbomodule.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/the-new-architecture/pillars-turbomodule.md b/docs/the-new-architecture/pillars-turbomodule.md index e005d721472..d32d3d4e2c6 100644 --- a/docs/the-new-architecture/pillars-turbomodule.md +++ b/docs/the-new-architecture/pillars-turbomodule.md @@ -268,3 +268,18 @@ Of interest in the `build.gradle` file: - 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**. + +## 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](/docs/pillars-codegen.md) + +:::caution +The code generated by the **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 to avoid any ABI incompatibility and to ensure that a consistent version of the **CodeGen** is used. +::: \ No newline at end of file From 07f72f71c827d3d61be22ccbda2c8ad1d7f18dc4 Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Mon, 20 Jun 2022 14:17:04 -0700 Subject: [PATCH 08/32] Must be named Spec --- docs/the-new-architecture/pillars-turbomodule.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/the-new-architecture/pillars-turbomodule.md b/docs/the-new-architecture/pillars-turbomodule.md index d32d3d4e2c6..b2068c0b7bc 100644 --- a/docs/the-new-architecture/pillars-turbomodule.md +++ b/docs/the-new-architecture/pillars-turbomodule.md @@ -65,12 +65,12 @@ There are two requirements the file containing this specification must meet: import type { TurboModule } from 'react-native/Libraries/TurboModule/RCTExport'; import { TurboModuleRegistry } from 'react-native'; -export interface RTNCalculatorSpec extends TurboModule { +export interface Spec extends TurboModule { add(a: number, b: number): Promise; } -export default (TurboModuleRegistry.get( +export default (TurboModuleRegistry.get( 'RTNCalculator' -): ?RTNCalculatorSpec); +): ?Spec); ``` @@ -80,13 +80,13 @@ export default (TurboModuleRegistry.get( import type { TurboModule } from 'react-native/Libraries/TurboModule/RCTExport'; import { TurboModuleRegistry } from 'react-native'; -export interface RTNCalculatorSpec extends TurboModule { +export interface Spec extends TurboModule { add(a: number, b: number): Promise; } -export default (TurboModuleRegistry.get( +export default (TurboModuleRegistry.get( 'RTNCalculator' -) as RTNCalculatorSpec | null); +) as Spec | null); ``` @@ -97,7 +97,7 @@ 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. +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. From 7c5fea3120cbe2ad8e1d634c50b22f51645552bf Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Mon, 20 Jun 2022 14:17:54 -0700 Subject: [PATCH 09/32] 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. --- .../pillars-turbomodule.md | 112 +++++++++++++++++- 1 file changed, 111 insertions(+), 1 deletion(-) diff --git a/docs/the-new-architecture/pillars-turbomodule.md b/docs/the-new-architecture/pillars-turbomodule.md index b2068c0b7bc..1c74b3d1c81 100644 --- a/docs/the-new-architecture/pillars-turbomodule.md +++ b/docs/the-new-architecture/pillars-turbomodule.md @@ -282,4 +282,114 @@ As first step for both iOS and Android, this guide shows how to execute manually :::caution The code generated by the **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 to avoid any ABI incompatibility and to ensure that a consistent version of the **CodeGen** is used. -::: \ No newline at end of file +::: + +### 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 the script 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. + +#### 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 + +@interface RTNCalculator : NSObject + +@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. + +:::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). +::: + +TODO: more description? \ No newline at end of file From 4b6d2cfd9cb5142bc3499ed1ca6706f5ef79d23d Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Mon, 20 Jun 2022 14:18:34 -0700 Subject: [PATCH 10/32] 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. --- .../pillars-turbomodule.md | 217 +++++++++++++++++- 1 file changed, 216 insertions(+), 1 deletion(-) diff --git a/docs/the-new-architecture/pillars-turbomodule.md b/docs/the-new-architecture/pillars-turbomodule.md index 1c74b3d1c81..6c61ec237c1 100644 --- a/docs/the-new-architecture/pillars-turbomodule.md +++ b/docs/the-new-architecture/pillars-turbomodule.md @@ -392,4 +392,219 @@ Then the `RCT_REMAP_METHOD` macro is used to expose the `add` method. 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). ::: -TODO: more description? \ No newline at end of file +TODO: more description? + +### 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 --rerun-tasks +``` + +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: + +TODO: ordering issue. I can't actually get codegen to work at this point. Only works once I add the native files following. Probably similar issue for Fabric guide. Including generated file structure for later ease of use: + +```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 three more files: + +1. An `AndroidManifest.xml` file. +2. A `RTNCalculatorModule.java` that implements the module. +4. A `RTNCalculatorPackage.java` that React Native uses to configure the library. + +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 +``` + +##### AndroidManifest.xml + +```xml title="AndroidManifest.xml" + + +``` + +This is a small manifest file that defines the package for our module. + + +##### 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 { + + CalculatorModule(ReactApplicationContext context) { + super(context); + } + + @Override + @NonNull + public String getName() { + return "RTNCalculator"; + } + + @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. + +##### CalculatorPackage.java + +```java 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 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("RTNCalculator")) { + return new CalculatorModule(reactContext); + } else { + return null; + } + } + + + @Override + public ReactModuleInfoProvider getReactModuleInfoProvider() { + return () -> { + final Map moduleInfos = new HashMap<>(); + moduleInfos.put( + "RTNCalculator", + new ReactModuleInfo( + "RTNCalculator", + "RTNCalculator", + false, // canOverrideExistingModule + false, // needsEagerInit + true, // hasConstants + false, // isCxxModule + false // isTurboModule + )); + return moduleInfos; + }; + } + +} +``` + +This is the last piece of Native Code for Android. It defines the Package object that will be used by the app to load the module. + +## 5. Adding the Fabric Component 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`. + From 1706d64be78da76d276133256e973a1c432b1f7c Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Mon, 20 Jun 2022 14:18:44 -0700 Subject: [PATCH 11/32] Include linking instructions from RNNArch repo --- .../pillars-turbomodule.md | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/docs/the-new-architecture/pillars-turbomodule.md b/docs/the-new-architecture/pillars-turbomodule.md index 6c61ec237c1..c9a1d4d4f7b 100644 --- a/docs/the-new-architecture/pillars-turbomodule.md +++ b/docs/the-new-architecture/pillars-turbomodule.md @@ -608,3 +608,51 @@ 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/build.gradle` file and update the file as it follows: + ```diff + "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" + ``` +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); + } + ``` From a5de9eee7d498351cae384147661a3c1f0e7ccf4 Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Mon, 20 Jun 2022 14:18:51 -0700 Subject: [PATCH 12/32] Add example JavaScript --- .../pillars-turbomodule.md | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/docs/the-new-architecture/pillars-turbomodule.md b/docs/the-new-architecture/pillars-turbomodule.md index c9a1d4d4f7b..582774fd4c4 100644 --- a/docs/the-new-architecture/pillars-turbomodule.md +++ b/docs/the-new-architecture/pillars-turbomodule.md @@ -656,3 +656,46 @@ Then, to manually link your new TurboModule: 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 [currentResult, setResult] = useState(null); + return ( + + + + 3+7={currentResult ?? '??'} + +