This doc contains the logs of the steps done to achieve the final result.
- [Setup] Create the colored-view folder and the package.json
- [Native Component] Create the JS import
- [Native Component] Create the iOS implementation
- [Native Component] Create the Android implementation
- [Native Component] Test The Native Component
- [Fabric Component] Add the JavaScript specs
- [Fabric Component] Set up CodeGen
- [Fabric Component] Update gradle
- [Fabric Component] Set up
podspec
file - [Fabric Component] Update the Native iOS code
- [Fabric Component] Android: Update the Native code to use two sourcesets
- [Fabric Component] Android: Refactor the code to use a shared implementation
- [Fabric Component] Unify JavaScript interface
- [Fabric Component] Test the Fabric Component
mkdir colored-view
touch colored-view/package.json
- Paste the following code into the
package.json
file
{
"name": "colored-view",
"version": "0.0.1",
"description": "Showcase Fabric component with backward compatibility",
"react-native": "src/index",
"source": "src/index",
"files": [
"src",
"android",
"ios",
"colored-view.podspec",
"!android/build",
"!ios/build",
"!**/__tests__",
"!**/__fixtures__",
"!**/__mocks__"
],
"keywords": ["react-native", "ios", "android"],
"repository": "https://github.com/<your_github_handle>/colored-view",
"author": "<Your Name> <your_email@your_provider.com> (https://github.com/<your_github_handle>)",
"license": "MIT",
"bugs": {
"url": "https://github.com/<your_github_handle>/colored-view/issues"
},
"homepage": "https://github.com/<your_github_handle>/colored-view#readme",
"devDependencies": {},
"peerDependencies": {
"react": "*",
"react-native": "*"
}
}
mkdir colored-view/src
touch colored-view/src/index.js
- Paste the following content into the
index.js
// @flow
import { requireNativeComponent } from 'react-native'
export default requireNativeComponent("ColoredView")
mkdir colored-view/ios
- Create a new file
ios/RNColoredViewManager.m
with the following code:#import <React/RCTViewManager.h> @interface RNColoredViewManager : RCTViewManager @end @implementation RNColoredViewManager RCT_EXPORT_MODULE(ColoredView) - (UIView *)view { return [[UIView alloc] init]; } RCT_CUSTOM_VIEW_PROPERTY(color, NSString, UIView) { [view setBackgroundColor:[self hexStringToColor:json]]; } - hexStringToColor:(NSString *)stringToConvert { NSString *noHashString = [stringToConvert stringByReplacingOccurrencesOfString:@"#" withString:@""]; NSScanner *stringScanner = [NSScanner scannerWithString:noHashString]; unsigned hex; if (![stringScanner scanHexInt:&hex]) return nil; int r = (hex >> 16) & 0xFF; int g = (hex >> 8) & 0xFF; int b = (hex) & 0xFF; return [UIColor colorWithRed:r / 255.0f green:g / 255.0f blue:b / 255.0f alpha:1.0f]; } @end
- In the
colored-view
folder, create acolored-view.podspec
file - Copy this code in the
podspec
file
require "json"
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
Pod::Spec.new do |s|
s.name = "colored-view"
s.version = package["version"]
s.summary = package["description"]
s.description = package["description"]
s.homepage = package["homepage"]
s.license = package["license"]
s.platforms = { :ios => "11.0" }
s.author = package["author"]
s.source = { :git => package["repository"], :tag => "#{s.version}" }
s.source_files = "ios/**/*.{h,m,mm,swift}"
s.dependency "React-Core"
end
- Create a folder
colored-view/android
- Create the module
build.gradle
filecolored-view/android/build-gradle
and add this code:buildscript { ext.safeExtGet = {prop, fallback -> rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback } repositories { google() gradlePluginPortal() } dependencies { classpath("com.android.tools.build:gradle:7.2.0") } } apply plugin: 'com.android.library' android { compileSdkVersion safeExtGet('compileSdkVersion', 31) defaultConfig { minSdkVersion safeExtGet('minSdkVersion', 21) targetSdkVersion safeExtGet('targetSdkVersion', 31) } } repositories { maven { // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm url "$projectDir/../node_modules/react-native/android" } mavenCentral() google() } dependencies { implementation 'com.facebook.react:react-native:+' }
- Create the
AndroidManifest
filecolored-view/android/src/main/AndroidManifest.xml
and add this code:<manifest xmlns:android="https://schemas.android.com/apk/res/android" package="com.rnnewarchitecturelibrary"> </manifest>
- Create the Fabric Component View
colored-view/android/src/main/java/com/rnnewarchitecturelibrary/ColoredView.java
and add this code:package com.rnnewarchitecturelibrary; import androidx.annotation.Nullable; import android.content.Context; import android.util.AttributeSet; import android.view.View; public class ColoredView extends View { public ColoredView(Context context) { super(context); } public ColoredView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); } public ColoredView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } }
- Create the component Manager
colored-view/android/src/main/java/com/rnnewarchitecturelibrary/ColoredViewManager.java
and add this code:package com.rnnewarchitecturelibrary; import androidx.annotation.Nullable; import com.facebook.react.module.annotations.ReactModule; import com.facebook.react.uimanager.SimpleViewManager; import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.annotations.ReactProp; import com.facebook.react.bridge.ReactApplicationContext; import android.graphics.Color; import java.util.Map; import java.util.HashMap; public class ColoredViewManager extends SimpleViewManager<ColoredView> { public static final String NAME = "ColoredView"; ReactApplicationContext mCallerContext; public ColoredViewManager(ReactApplicationContext reactContext) { mCallerContext = reactContext; } @Override public String getName() { return NAME; } @Override public ColoredView createViewInstance(ThemedReactContext context) { return new ColoredView(context); } @ReactProp(name = "color") public void setColor(ColoredView view, String color) { view.setBackgroundColor(Color.parseColor(color)); } }
- Create the module Package descriptor file
colored-view/android/src/main/java/com/rnnewarchitecturelibrary/ColoredViewPackage.java
and add this code:package com.rnnewarchitecturelibrary; import com.facebook.react.ReactPackage; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.uimanager.ViewManager; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class ColoredViewPackage implements ReactPackage { @Override public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { List<ViewManager> viewManagers = new ArrayList<>(); viewManagers.add(new ColoredViewManager(reactContext)); return viewManagers; } @Override public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) { return Collections.emptyList(); } }
- At the same level of colored-view run
npx react-native init OldArchitecture --version 0.70.0-rc.2
cd OldArchitecture && yarn add ../colored-view
- If running on iOS, install the dependencies with
cd ios && bundle install && bundle exec pod install && cd ..
- Run the app:
- For iOS:
npx react-native run-ios
- For android:
npx react-native run-android
- For iOS:
- Open
OldArchitecture/App.js
file and replace the content with:/** * Sample React Native App * https://github.com/facebook/react-native * * @format * @flow strict-local */ import React from 'react'; import type {Node} from 'react'; import { SafeAreaView, StatusBar, Text, View, } from 'react-native'; import ColoredView from 'colored-view/src/index' const App: () => Node = () => { return ( <SafeAreaView> <StatusBar barStyle={'dark-content'} /> <ColoredView color="#FF0099" style={{marginLeft:10, marginTop:20, width:100, height:100}}/> </SafeAreaView> ); }; export default App;
- Play with the
color
property to see the View background color change
Note: OldArchitecture app has not been committed not to pollute the repository.
touch colored-view/src/ColoredViewNativeComponent.js
- Paste the following code:
// @flow import type {ViewProps} from 'react-native/Libraries/Components/View/ViewPropTypes'; import type {HostComponent} from 'react-native'; import { ViewStyle } from 'react-native'; import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent'; type NativeProps = $ReadOnly<{| ...ViewProps, color: string |}>; export default (codegenNativeComponent<NativeProps>( 'ColoredView', ): HostComponent<NativeProps>);
- Open the
colored-view/package.json
- Add the following snippet at the end of it:
, "codegenConfig": { "name": "RNColoredViewSpec", "type": "components", "jsSrcsDir": "src" }
- Open the
colored-view/android/build.gradle
file and update it as it follows:- At the beginning of the file, add the following lines:
+ def isNewArchitectureEnabled() { + return project.hasProperty("newArchEnabled") && project.newArchEnabled == "carview.php?tsp=true" + } + apply plugin: 'com.android.library' + if (isNewArchitectureEnabled()) { + apply plugin: 'com.facebook.react' + }
- At the beginning of the file, add the following lines:
- Open the
colored-view/colored-view.podspec
file - Before the
Pod::Spec.new do |s|
add the following code:folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'
- Before the
end
tag, add the following code# This guard prevent to install the dependencies when we run `pod install` in the old architecture. if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1" s.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"", "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" s.dependency "RCTRequired" s.dependency "RCTTypeSafety" s.dependency "ReactCommon/turbomodule/core" end
- In the
colored-view/ios
folder, rename theRNColoredViewManager.m
intoRNColoredViewManager.mm
- Create a new header and call it
RNColoredView.h
- Paste the following code in the new header:
// This guard prevent this file to be compiled in the old architecture. #ifdef RCT_NEW_ARCH_ENABLED #import <React/RCTViewComponentView.h> #import <UIKit/UIKit.h> #ifndef NativeComponentExampleComponentView_h #define NativeComponentExampleComponentView_h NS_ASSUME_NONNULL_BEGIN @interface RNColoredView : RCTViewComponentView @end NS_ASSUME_NONNULL_END #endif /* NativeComponentExampleComponentView_h */ #endif /* RCT_NEW_ARCH_ENABLED */
- Create a new file and call it
RNColoredView.mm
- Paste the following code in the new file:
// This guard prevent the code from being compiled in the old architecture #ifdef RCT_NEW_ARCH_ENABLED #import "RNColoredView.h" #import <react/renderer/components/RNColoredViewSpec/ComponentDescriptors.h> #import <react/renderer/components/RNColoredViewSpec/EventEmitters.h> #import <react/renderer/components/RNColoredViewSpec/Props.h> #import <react/renderer/components/RNColoredViewSpec/RCTComponentViewHelpers.h> #import "RCTFabricComponentsPlugins.h" using namespace facebook::react; @interface RNColoredView () <RCTColoredViewViewProtocol> @end @implementation RNColoredView { UIView * _view; } + (ComponentDescriptorProvider)componentDescriptorProvider { return concreteComponentDescriptorProvider<ColoredViewComponentDescriptor>(); } - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { static const auto defaultProps = std::make_shared<const ColoredViewProps>(); _props = defaultProps; _view = [[UIView alloc] init]; self.contentView = _view; } return self; } - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps { const auto &oldViewProps = *std::static_pointer_cast<ColoredViewProps const>(_props); const auto &newViewProps = *std::static_pointer_cast<ColoredViewProps const>(props); if (oldViewProps.color != newViewProps.color) { NSString * colorToConvert = [[NSString alloc] initWithUTF8String: newViewProps.color.c_str()]; [_view setBackgroundColor:[self hexStringToColor:colorToConvert]]; } [super updateProps:props oldProps:oldProps]; } Class<RCTComponentViewProtocol> ColoredViewCls(void) { return RNColoredView.class; } - hexStringToColor:(NSString *)stringToConvert { NSString *noHashString = [stringToConvert stringByReplacingOccurrencesOfString:@"#" withString:@""]; NSScanner *stringScanner = [NSScanner scannerWithString:noHashString]; unsigned hex; if (![stringScanner scanHexInt:&hex]) return nil; int r = (hex >> 16) & 0xFF; int g = (hex >> 8) & 0xFF; int b = (hex) & 0xFF; return [UIColor colorWithRed:r / 255.0f green:g / 255.0f blue:b / 255.0f alpha:1.0f]; } @end #endif
- Remove the file
colored-view/android/src/main/java/com/rnnewarchitecturelibrary/ColoredViewManager.java
. - Open the
colored-view/android/build.gradle
file and add the following lines:defaultConfig { minSdkVersion safeExtGet('minSdkVersion', 21) targetSdkVersion safeExtGet('targetSdkVersion', 31) + buildConfigField("boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()) + } + + sourceSets { + main { + if (isNewArchitectureEnabled()) { + java.srcDirs += ['src/newarch'] + } else { + java.srcDirs += ['src/oldarch'] + } + } + }
- Create a View Manager for the New Architecture
colored-view/android/src/newarch/java/com/rnnewarchitecturelibrary/ColoredViewManager.java
(Notice thesrc/newarch
segment in the path) with this code:package com.rnnewarchitecturelibrary; import android.graphics.Color; 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.ColoredViewManagerDelegate; import com.facebook.react.viewmanagers.ColoredViewManagerInterface; @ReactModule(name = ColoredViewManager.NAME) public class ColoredViewManager extends SimpleViewManager<ColoredView> implements ColoredViewManagerInterface<ColoredView> { public static final String NAME = "ColoredView"; private final ViewManagerDelegate<ColoredView> mDelegate; public ColoredViewManager(ReactApplicationContext context) { mDelegate = new ColoredViewManagerDelegate<>(this); } @Nullable @Override protected ViewManagerDelegate<ColoredView> getDelegate() { return mDelegate; } @NonNull @Override public String getName() { return NAME; } @NonNull @Override protected ColoredView createViewInstance(@NonNull ThemedReactContext context) { return new ColoredView(context); } @Override @ReactProp(name = "color") public void setColor(ColoredView view, @Nullable String color) { view.setBackgroundColor(Color.parseColor(color)); } }
- Create a View Manager for the Old Architecture
colored-view/android/src/oldarch/java/com/rnnewarchitecturelibrary/ColoredViewManager.java
(Notice thesrc/oldarch
segment in the path) with this code:package com.rnnewarchitecturelibrary; import androidx.annotation.Nullable; import com.facebook.react.module.annotations.ReactModule; import com.facebook.react.uimanager.SimpleViewManager; import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.annotations.ReactProp; import com.facebook.react.bridge.ReactApplicationContext; import android.graphics.Color; import java.util.Map; import java.util.HashMap; public class ColoredViewManager extends SimpleViewManager<ColoredView> { public static final String NAME = "ColoredView"; ReactApplicationContext mCallerContext; public ColoredViewManager(ReactApplicationContext reactContext) { mCallerContext = reactContext; } @Override public String getName() { return NAME; } @Override public ColoredView createViewInstance(ThemedReactContext context) { return new ColoredView(context); } @ReactProp(name = "color") public void setColor(ColoredView view, String color) { view.setBackgroundColor(Color.parseColor(color)); } }
- Create a common implementation in
colored-view/android/src/main/java/com/rnnewarchitecturelibrary/ColoredViewManagerImpl.java
with the following code:package com.rnnewarchitecturelibrary; import androidx.annotation.Nullable; import com.facebook.react.uimanager.ThemedReactContext; import android.graphics.Color; public class ColoredViewManagerImpl { public static final String NAME = "ColoredView"; public static ColoredView createViewInstance(ThemedReactContext context) { return new ColoredView(context); } public static void setColor(ColoredView view, String color) { view.setBackgroundColor(Color.parseColor(color)); } }
- Update the
ColoredViewManager
in thesrc/newarch
path:import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.ViewManagerDelegate; import com.facebook.react.uimanager.annotations.ReactProp; import com.facebook.react.viewmanagers.ColoredViewManagerDelegate; import com.facebook.react.viewmanagers.ColoredViewManagerInterface; - @ReactModule(name = ColoredViewManager.NAME) + @ReactModule(name = ColoredViewManagerImpl.NAME) public class ColoredViewManager extends SimpleViewManager<ColoredView> implements ColoredViewManagerInterface<ColoredView> { - public static final String NAME = "ColoredView"; - private final ViewManagerDelegate<ColoredView> mDelegate; public ColoredViewManager(ReactApplicationContext context) { mDelegate = new ColoredViewManagerDelegate<>(this); } @Nullable @Override protected ViewManagerDelegate<ColoredView> getDelegate() { return mDelegate; } @NonNull @Override public String getName() { - return NAME; + return ColoredViewManagerImpl.NAME; } @NonNull @Override protected ColoredView createViewInstance(@NonNull ThemedReactContext context) { - return new ColoredView(context); + return ColoredViewManagerImpl.createViewInstance(context); } @Override @ReactProp(name = "color") public void setColor(ColoredView view, @Nullable String color) { - view.setBackgroundColor(Color.parseColor(color)); + ColoredViewManagerImpl.setColor(view, color); } }
- Update the
ColoredViewManager
in thesrc/oldarch
path:public class ColoredViewManager extends SimpleViewManager<ColoredView> { - public static final String NAME = "ColoredView"; - ReactApplicationContext mCallerContext; public ColoredViewManager(ReactApplicationContext reactContext) { mCallerContext = reactContext; } @Override public String getName() { - return NAME; + return ColoredViewManagerImpl.NAME; } @Override public ColoredView createViewInstance(ThemedReactContext context) { - return new ColoredView(context); + return ColoredViewManagerImpl.createViewInstance(context); } @ReactProp(name = "color") public void setColor(ColoredView view, String color) { - view.setBackgroundColor(Color.parseColor(color)); + ColoredViewManagerImpl.setColor(view, color); } }
- Open the
src/index.js
file - Replace the code with the following:
// @flow export default require("./ColoredViewNativeComponent").default;
- At the same level of
colored-view
runnpx react-native init NewArchitecture --version 0.70.0-rc.2
cd NewArchitecture && yarn add ../colored-view
- If running on iOS, install the dependencies with
cd ios && bundle install && RCT_NEW_ARCH_ENABLED=1 bundle exec pod install && cd ..
- Open the
NewArchitecture/android/gradle.properties
and update them as it follows:- newArchEnabled=false + newArchEnabled=true
- Run the app:
- For iOS:
npx react-native run-ios
- For Android:
npx react-native run-android
- For iOS:
- Open
NewArchitecture/App.js
file and replace the content with the same file used for theOldArchitecture
. - Play with the
color
property to see the View background color change
Note: NewArchitecture app has not been committed not to pollute the repository.