Common Development Scenarios
How Do I Use onForeGround and onBackGround?
onForeground and onBackground are used to solve the problem that AppState.addEventListener('change', this.handleAppStateChange) is not called back when the page redirects or returns. The following describes the usage:
-
When
routeris used-
If the
RNAbilityclass is used, the implementation is as follows:-
Import the
RNAbilityclass to the page decorated by@Entry.import {RNAbility} from '@rnoh/react-native-openharmony'; ... @Entry @Component export struct ComponentName{ @StorageLink('RNAbility') rnAbility: RNAbility | undefined = undefined ... } -
Call
rnAbility?.onForeground()andrnAbility?.onBackground()in lifecycle callbacksonPageShowandonPageHide.onPageShow(){ this.rnAbility?.onForeground(); } onPageHide(){ this.rnAbility?.onBackground(); }
-
-
If
RNAbilityis not used, the implementation is as follows:-
Import the
RNInstancesCoordinatorclass to the page decorated by@Entry.import {RNInstancesCoordinator} from '@rnoh/react-native-openharmony'; ... @Entry @Component export struct ComponentName{ @StorageLink('RNInstancesCoordinator') private rninstancesCoordinator: RNInstancesCoordinator | undefined = undefined ... } -
Call
rninstancesCoordinator?.onForeground()andrninstancesCoordinator?.onBackground()in lifecycle callbacksonPageShowandonPageHide.onPageShow(){ this.rninstancesCoordinator?.onForeground() } onPageHide(){ this.rninstancesCoordinator?.onBackground() }
-
-
-
When
Navigationis used- If the
RNAbilityclass is used, the implementation is as follows:
-
Import the
RNAbilityclass to the custom component withNavDestination. This step is the same as that whenrouteris used. -
Call
rnAbility?.onForeground()in theonShowncallback event ofNavDestination, and callrnAbility?.onBackground()in theonHiddencallback event.... build(){ NavDestination(){ ... } .onShown(() => { this.rnAbility?.onForeground(); }) .onHidden(() =>{ this.rnAbility?.onBackground(); }) }
-
If
RNAbilityis not used, the implementation is as follows:-
Import the
RNInstancesCoordinatorclass to the custom component withNavDestination. This step is the same as that whenrouteris used. -
Call
rninstancesCoordinator?.onForeground()in theonShowncallback event ofNavDestination, and callrninstancesCoordinator?.onBackground()in theonHiddencallback event.... build(){ NavDestination(){ ... } .onShown(() => { this.rninstancesCoordinator?.onForeground() }) .onHidden(() =>{ this.rninstancesCoordinator?.onBackground() }) }
-
- If the
How Do I Process Back Press Events?
onBackPress is used to handle a back press event. The basic principle is to call the onBackPress capability of the framework in the native onBackPress lifecycle callback, and ensure return true. (If true is returned, the page handles the back logic by itself, rather than call the native default back event capability. If false is returned, the native default route back logic is used. The default value is false.) Then, the back press event logic is handled by the framework, which further sends the event to the JS. Therefore, the final back press event logic is implemented in the JS.
-
When
routeris used-
Single-
RNInstancescenario-
Import the
RNOHCoreContextclass to the page decorated by@Entry.import {RNOHCoreContext} from '@rnoh/react-native-openharmony'; ... @Entry @Component export struct ComponentName{ @StorageLink('RNOHCoreContext') private rnohCoreContext: RNOHCoreContext | undefined = undefined ... } -
Call
this.rnohCoreContext!.dispatchBackPress()in lifecycle callbackonBackPressandreturn true.onBackPress(): boolean | undefined{ this.rnohCoreContext!.dispatchBackPress(); return true; }
-
-
Multi-
RNInstancescenarioIf the project has multiple
RNInstances,rnohCoreContext!.dispatchBackPress()cannot be called in theonBackPressedcallback event. You need to obtain theRNInstanceinstance that responds to the back press event in the foreground based on the actual service scenario, and call theonBackPressmethod of the instance.// router onBackPress(): boolean{ // Obtain the rnInstance that responds to the back press event. ... rnInstance.onBackPress(); return true; }
-
-
When
Navigationis used-
Single-
RNInstancescenario-
Import the
RNOHCoreContextclass to the custom component withNavDestination. This step is the same as that whenrouteris used. -
Call
rnohCoreContext!.dispatchBackPress()in theonBackPressedcallback event ofNavDestination.... build(){ NavDestination(){ ... } .onBackPressed(() => { this.rnohCoreContext!.dispatchBackPress(); return true; }) }
-
-
Multi-
RNInstancescenarioThe method is the same as that when
routeris used. TheRNInstancethat responds to the back press event needs to be obtained.// Navigation ... build(){ NavDestination(){ ... } .onBackPressed(() => { // Obtain the rnInstance that responds to the back press event. ... rnInstance.onBackPress(); return true; }) }
-
-
Default back press of the React Native (RN) page
- In
RNAbility, there is adefaultBackPressHandlermethod, which will be used if no special handling is performed on the RN page orexitAppis called. You can override this method to customize the default back press. - If you do not use
RNAbilitybut perform custom extension, you need to passdefaultBackPressHandleras a parameter to the framework when building theRNInstancesCoordinatorobject.
- In
How Do I Split Bundles by Using React Native?
To split bundles, you need to configure createModuleIdFactory and processModuleFilter in serializer of metro.config.
-
createModuleIdFactory: Metro supports the configuration of custom module ID using this method. The string ID is also supported, which is used to generate a module ID of therequirestatement. The type is() => (path: string) => number(function with return parameters).pathindicates the complete path of each module. Another purpose of this method is to generate the same ID for the same module during multiple bundling. In this way, the module can be found based on the ID when the version is updated next time. -
processModuleFilter: Filters out unnecessary modules based on the given conditions. The type is(module: Array<Module>) => boolean, wheremoduleindicates the output module, which contains the corresponding parameters. You can determine whether to filter the current module based on the returned Boolean value.falsemeans that the module is filtered, and is not bundled.
For details, see RN JS Bundle.
How Do I Load Multiple Bundles?
The procedure for loading multiple bundles is as follows:
- A single bundle is split into basic bundle and service bundle.
- During project initialization, create the required
RNInstanceinstance and call therunJSBundleAPI to load the basic bundle. - When loading the corresponding service module, call the
runJSBundleAPI to load the service bundle.
For details, see MultiBundleSample.
How Do I Use Components Based on the ArkUI Declarative Paradigm in the C-API Version?
To use components based on the ArkUI declarative paradigm in the C-API version, perform the following steps:
- Use the ArkUI declarative paradigm to implement custom components.
- Use custom components in the service code.
- Complete the factory method of a custom component and pass it to
RNApporRNSurface. - Create
CustomRNComponentFrameNodeFactoryand pass it toRNInstance. For details, see Usage of the C-API Component Hybrid Solution. - Use
RNApporRNSurfaceto start React Native for OpenHarmony.
How Do I Enable LogBox?
If you use the RN framework started by RNApp, LogBox is enabled by default. If you use the RN framework started by RNSurface, you need to create a RNComponentContext and a LogBox constructor, add the following event emitter to devToolsController of context, and start and close the LogBox pop-up window.
this.logBoxDialogController = new CustomDialogController({
cornerRadius: 0,
customStyle: true,
alignment: DialogAlignment.TopStart,
backgroundColor: Color.Transparent,
builder: LogBoxDialog({
ctx: RNComponentContext,
rnInstance: this.rnInstance,
initialProps: this.initialProps,
buildCustomComponent: this.buildCustomComponent,
})
})
···
this.rnInstance.getTurboModule<LogBoxTurboModule>(LogBoxTurboModule.NAME).eventEmitter.subscribe("SHOW", () => {
this.logBoxDialogController.open();
})
this.rnInstance.getTurboModule<LogBoxTurboModule>(LogBoxTurboModule.NAME).eventEmitter.subscribe("HIDE", () => {
this.logBoxDialogController.close();
})
How Do I Enable Metro?
-
Ensure that the OpenHarmony bundle parameters have been configured. For details, see How Do I Set OpenHarmony Bundle Parameters?.
-
If you use the RN framework started by
RNApp, skip this step because Metro is enabled by default. If you use the RN framework started byRNSurface, you need to create aRNComponentContext, add the following event emitter todevToolsControllerofcontext, delete the originalRNInstance, and restart a newRNInstanceto enable the hot reloading function.this.ctx.devToolsController.eventEmitter.subscribe("RELOAD", async () => { this.cleanUp(); ths.init(); }) -
Configure
jsBundleProviderof Metro.// index.ets Use RNApp to load MetroJSBundleProvider. build() { RNApp({ ··· jsBundleProvider: new TraceJSBundleProviderDecorator( new AnyJSBundleProvider([ new MetroJSBundleProvider(), ··· ]), this.rnohCoreContext.logger), ··· }) }··· // Use RNInstance to load MetroJSBundleProvider. await RNInstance.runJSBundle(new MetroJSBundleProvider()) .then(()->{ isReady = true; ··· }) ··· -
Connect to a real device and run the following commands:
hdc rport tcp:8081 tcp:8081npm run start
How Do I Set OpenHarmony Bundle Parameters?
When running React Native, Metro Bundler packs JavaScript code into one or more bundles and provides them for devices to execute at runtime. To customize the behavior of Metro Bundler, you need to create a configuration file that tells Metro Bundler how to handle the project to be packaged.
-
Import two functions from
@react-native/metro-config:const {mergeConfig, getDefaultConfig} = require('@react-native/metro-config');mergeConfig: merges multiple configurations.getDefaultConfig: gets the default Metro configuration.
-
Import the
createHarmonyMetroConfigfunction fromreact-native-harmony/metro.configto create specific configurations applicable to the OpenHarmony platform.const {createHarmonyMetroConfig} = require('@react-native-oh/react-native-harmony/metro.config'); -
Use the
@type {import("metro-config").ConfigT}comment to indicate that the file type isConfigTof themetro-configmodule. This helps editors and code analysis tools understand the structure and type of the file./** * @type {import("metro-config").ConfigT} */ -
Define a
configobject, in whichtransformerspecifies the transform options.const config = { transformer: { getTransformOptions: async () => ({ transform: { experimentalImportSupport: false, inlineRequires: true, }, }), }, };getTransformOptions: gets transform options asynchronously. The following options are configured:experimentalImportSupport: Set it tofalse, indicating that experimental import support is not used.inlineRequires: Set it totrue, indicating that therequirestatement is inlined to the bundle.
-
Call the
mergeConfigfunction to merge the default configuration, OpenHarmony configuration, and custom configurations into a final configuration and export it for the Metro Bundler to use.The default metro-config is as follows:
/* Import functions. */ const {mergeConfig, getDefaultConfig} = require('@react-native/metro-config'); const {createHarmonyMetroConfig} = require('@react-native-oh/react-native-harmony/metro.config'); /** * @type {import("metro-config").ConfigT} */ /* Define the configuration.*/ const config = { transformer: { getTransformOptions: async () => ({ transform: { experimentalImportSupport: false, inlineRequires: true, }, }), }, }; /* Call the mergeConfig function to merge the default configuration, OpenHarmony configuration, and custom configurations into a final configuration and export it for the Metro Bundler to use.*/ module.exports = mergeConfig( getDefaultConfig(__dirname), createHarmonyMetroConfig({ reactNativeHarmonyPackageName: 'react-native-harmony', }), config, );
How Do I Load a Bundle or Image from the Sandbox directory?
-
Loading a
bundlefrom the sandbox directoryApplication sandbox is an isolation mechanism used to prevent malicious data access through path traversal. With this mechanism, only the application sandbox directory is visible to an application.
During the development and debugging process of an application, you need to push files to the application sandbox directory for intra-application access or for testing purposes. You can use either of the following methods to push files:
Method 1: Use DevEco Studio to push the files to the application installation directory. For details, see Resource Categories and Access. Method 2: Use the hdc tool to push files to the application sandbox directory on the device, which can be implemented using the following send command. This method is more flexible. The push command is as follows. The sandbox directory can be queried by pushing files to the application sandbox.
hdc file send ${Local directory for pushing files} ${Sandbox directory}To load a
bundlefrom the sandbox directory, you need to usenew FileJSBundleProvider('bundlePath')in thejsBundlePrividerparameter of RNApp to register thebundlewith the framework and run thebundle.In the
Index.etsfile under theMyApplication/entrydirectory, passjsBundleProviderto load the bundle when creating anRNApp. As shown in the code, threeBundleProviders are passed in, indicating that thebundleis loaded byMetro, sandbox directory, and local mode respectively. If thebundlefails to be loaded byMetro, it should be loaded in the sequence ofJSBundleProviders until the loading is successful or fails in all modes.// index.ets Use RNApp to load FileJSBundleProvider. build() { RNApp({ ··· jsBundleProvider: new TraceJSBundleProviderDecorator( new AnyJSBundleProvider([ new FileJSBundleProvider('/data/storage/el2/base/files/bundle.harmony.js'), ··· ]), this.rnohCoreContext.logger), ··· }) }··· // Use RNInstance to load FileJSBundleProvider. await RNInstance.runJSBundle(new FileJSBundleProvider('/data/storage/el2/base/files/bundle.harmony.js')) .then(()->{ isReady = true; ··· }) ··· -
Loading an image from the sandbox directory
This involves how the sandbox mechanism processes and loads an image. Each application runs in its own sandbox environment. In the sandbox, applications can access their own files and directories.
-
Obtain the image directory: Before loading an image from the sandbox directory, obtain the directory of the image in the sandbox. The directory usually starts with
file:///.const FILE_URI = 'file:///data/storage/el2/base/files/xxx.jpg'; -
Load an image: Use
uriof thesourceattribute of theImagecomponent to specify the absolute path. Or use the corresponding API or library to load and display an image.<Image style={{borderRadius: 8, borderWidth: 1, height: 150}} source={{uri: FILE_URI}} /> -
Precautions: Currently, the directories for loading an image by local mode or sandbox are encoded in different ways. For an image loaded by local mode, the image is searched from the rawfile/assets directory, so the image resources need to be stored under this directory. For an image loaded by sandbox, the image is directly searched from the bundle directory at the same level, and no additional assets directory is required.
For details about how to read a file from the sandbox, see the sandbox sample.
-
How Do I Generate a .hbc Bundle?
To convert the bundle to .hbc format, perform the following steps:
-
Make sure you have installed the
Hermestoolchain. The tool is provided in thenode_modules\react-native\sdks\hermescdirectory of the sample project by default. If it is not available, run the following command to install it on the terminal:npm install -g hermes-engine -
To convert the bundle to .hbc format, run the following command on the terminal:
hermesc --emit-binary input_bundle.jsbundle -out output_bytecode.hbc -
In the preceding command,
input_bundle.jsbundleindicates the name of the bundle to be converted, andoutput_bytecode.hbcindicates the name of the converted .hbc file. -
The following describes a demo script file for compiling
Hermes.// Use Hermes to convert the bundle to .hbc format. echo "*************************** start hermesc *********************************" // Prompt the user to enter the directory of the JavaScript bundle, and save the entered directory in the dir variable. read -p "Enter the JS bundle directory: "dir // Define a read-only variable bundle_hbc to store the directory of the translated Hermes bytecode file. It is used to create a folder named bundle_hbc in the parent directory of the input dir. readonly bundle_hbc=$dir/../bundle_hbc // Recursively delete the directory that stores the Hermes bytecode to ensure that a new bytecode file is generated from the beginning each time the script is executed. rm -rf $bundle_hbc // Create a directory for storing the Hermes bytecode file. mkdir -p $bundle_hbc // Create a file to store different types of bundles. mkdir -p $bundle_hbc/basic mkdir -p $bundle_hbc/cp // Run the hermesc command to convert the specified JavaScript bundle file into a Hermes bytecode file (.hbc format). hermesc --emit-binary $dir/basic/basic.harmony.bundle -out $bundle_hbc/basic/basic.harmony.hbc hermesc --emit-binary $dir/cp/goods.harmony.bundle -out $bundle_hbc/cp/goods.harmony.hbc hermesc --emit-binary $dir/cp/homepage.harmony.bundle -out $bundle_hbc/cp/homepage.harmony.hbc echo "*************************** end hermesc *********************************" echo "Bundle export directory: $bundle_hbc" -
After the preceding steps are complete, the bundle is converted to .hbc format.

How Do I Reduce the HAP Size?
A large OpenHarmony Ability Package (HAP) may occupy more resources during application download, installation, and running, affecting user experience. Therefore, when building or compiling a OpenHarmony application, you can adjust or change the compilation settings to reduce the size of the generated HAP.
-
Set a secure compilation parameter in the
CMakeLists.txtfile to reduce the HAP size.The secure compilation parameter is as follows:
set(CMAKE_CXX_FLAGS "-s")-s: tells the compiler to remove the symbol table and debugging information during linking to reduce the size of the executable file.
-
Set the parameters in the
build-profile.json5file./* path: path of the CMakeLists.txt file, which is usually used to specify the location of the CMake build file. arguments: empty, indicating that there are no additional build arguments. cppFlags: The C++ compilation option -s is set here, which can be used to remove the symbol table and debugging information to reduce the size of the generated executable file. */ "buildOption": { "externalNativeOptions": { "path": "./src/main/cpp/CMakeLists.txt", "arguments": "", "cppFlags": "-s", }, }, -
Modify the compilation option to reduce the HAP size.
On the
Build Modetab page, selectrelease.
-
Compress the .so file to reduce the HAP size. Modify the
compressNativeLibsfield in themodule.json5configuration file of the application module by referring to Reducing the Size of Application Packages, set the value to true, and recompile and bundle the application.{ "module": { // ... "compressNativeLibs": true // Indicates that the libs are compressed for storage. } }
How Do I Adapt to Foldable Screens?
T.B.D.
How Do I Enable Secure Compilation by Using a .so File?
Common parameters include strip, NO Rpath/Runpath, and SP.
-
strip coverage(remove the symbol table): Symbols play an important role in the linking process. The essence of the linking process is to "stick" multiple different target files together. Symbols can be regarded as the adhesive of links. The entire linking process is completed based on symbols. After linking is complete, the symbol table will no longer affect the running of executable files. Instead, it can be exploited by attackers. Therefore, removing the symbol table can help defend against hacker attacks. In addition, it can help reduce the file size. Add the following code underexternalNativeOptionsofbuild-profile.json5in theentrymodule:"cppFlags": "-s" -
NO Rpath/Runpath coverage(dynamic library search path): You can specify the search path of the dynamic link library during program running during compilation, to prevent attacks by malicious replacement of some dynamic libraries. Add the following code toCMakeLists.txtin theentrymodule:set(CMAKE_SKIP_BUILD_RPATH TRUE) -
SP coverage(stack protection): When a buffer overflow attack vulnerability exists, an attacker can override the return address on the stack to hijack the program control flow. When stack protection is enabled, acanary wordis inserted between the buffer and the control flow. Generally, thiscanary wordis overridden when the attacker overrides the return address. By checking thecanary word, you can determine whether an overflow attack occurs. Add the following code toCMakeLists.txtin theentrymodule:set(CMAKE_CXX_FLAGS "-fstack-protector-strong -Wl,-z,relro,-z,now,-z,noexecstack -s -fPIE -pie")- -fstack-protector-strong: enables the stack protection mechanism to help prevent buffer overflow attacks.
- -Wl,-z,relro,-z,now,-z,noexecstack: sets some security options. These parameters are passed to the linker ID. Specifically:
- -z,relro: changes the relocation table of the read-only segment to read-only.
- -z,now: executes all dynamic links of the program immediately to reduce potential vulnerabilities.
- -z,noexecstack: prevents code execution on the stack to reduce the attack surface of code execution.
- -s: tells the compiler to remove the symbol table and debugging information during linking to reduce the size of the executable file.
- -fPIE -pie: generates the position-independent executable (PIE) of an executable file. The PIE can improve program security, so that the loading address of the program in the memory is random, thereby increasing difficulty for attackers.
Note: False reports may occur during SP scanning. For example, the corresponding CPP code does not contain local arrays or local variables that are the R-values or function parameters of assignment statements, and no protection code is inserted.
How Do I Set the Center Alignment of Text After Setting the lineHeight Attribute?
By default, the bottom-layer font engine does not apply center alignment based on the row height. You need to configure the following parameters in the module.json5 file in the project entry directory to enable center alignment:
"metadata": [
{
"name": "half_leading",
"value": "true"
}
]
How Do I Set TextInput to Display Pinyin?
Currently, you need to configure the following parameters in the module.json5 file in the project entry directory to display pinyin:
"metadata": [
{
"name": "can_preview_text",
"value": "true"
}
]
How Do I Control Whether the Page Avoids the Keyboard?
-
Method 1: Use the component layout capability of RN to avoid the keyboard. Use the
KeyboardAvoidingViewcomponent as the container so that the layout is automatically adjusted based on the keyboard pop-up height. -
Method 2: Use the default behavior of ArkUI to avoid the keyboard. Currently, the
expandSafeArea([SafeAreaType.KEYBOARD])attribute is configured forRNAppby default. Therefore, you can only adjust the internal layout of the RN page to avoid the keyboard. There is no related configuration onRNSurface, and the ArkUI component automatically avoids the keyboard. -
You can integrate
RNSurface/RNAppto set theexpandSafeArea([SafeAreaType.KEYBOARD])attribute. Use different combinations of RN layout components to customize the keyboard avoidance effect.
How Do I Obtain RNInstance and RootTag Corresponding to RNSurface?
You can obtain RNInstance and rootTag through the updateViewRootTag callback function in RNSurface.
In the following example, an RN page is built with two RNSurfaces that use the same RNInstance but different RootTags. There is a button between two RNSurfaces. The first RNSurface is S1 and the second RNSurface is S2. The updateViewRootTag API enables the following effect: When the button is clicked, a message is sent to all RNInstances. The message is processed by the corresponding RNSurface only when the RootTag of the specified RNSurface in the message is consistent.

The following code is used to build the RN page:
build() {
NavDestination() {
if (this.isBundleReady) {
Column() {
Row() {
RNSurface({
surfaceConfig: {
appKey: 'AppO',
},
ctx: new RNComponentContext(
RNOHContext.fromCoreContext(this.rnohCoreContext!, this.instance),
wrappedCustomRNComponentBuilder,
wrapBuilder(buildRNComponentForTag),
new Map()
),
updateViewRootTag: (rnInstance: RNInstance, rootViewTag: Tag) => {
// Save the RNInstance and RootTag of the current RNSurface.
this.receivedRootTag = rootViewTag;
this.receivedInstance = rnInstance;
}
})
}
.height("45%")
Row() {
Button("CLICK ME")
.onClick(() => {
// Send a message to RN.
this.instance?.emitDeviceEvent("clickEvent", { params: ({
rootTag: this.receivedRootTag
} as Params) })
})
}
.height("10%")
Row() {
RNSurface({
surfaceConfig: {
appKey: 'AppT',
},
ctx: new RNComponentContext(
RNOHContext.fromCoreContext(this.rnohCoreContext!, this.instance),
wrappedCustomRNComponentBuilder,
wrapBuilder(buildRNComponentForTag),
new Map()
),
})
}
.height("45%")
}
.width('100%')
.height('100%')
}
}
}
It can be seen that, to build S1, you can obtain RNInstance and RootTag corresponding to the RNSurface through updateViewRootTag, bind the Click event through the button, and transfer the corresponding RootTag.
Only when the RootTag of the native Click event in the frontend code is the same as the current RootTag, the message can be processed.
const rootTag = useContext(RootTagContext);
const [msg, setMsg] = useState('')
DeviceEventEmitter.addListener('clickEvent', e => {
setMsg(`The message was ${e.params.rootTag === rootTag ? '' : 'not'} sent to me.`)
const timer = setTimeout(() => {
setMsg('')
clearTimeout(timer)
}, 1000)
})
The following figure shows the effect. After the button is clicked, both S1 and S2 receive the message, but only the RootTag of S1 meets the requirement after comparison. Therefore, the message is correctly replied.

The preceding code snippet is extracted from the RootTagSample project.
Precautions for Using a Custom Bundle Command
-
The current version provides the
bundle-harmonycommand to directly use the HarmOpenHarmonyonyOS-based RN bundle script. You only need to set correct parameters. -
If you want to eliminate command difference across platforms and use a custom bundle command instead of
bundle-harmony, you can refer to the bundling process in the react-native-harmony-cli/dist/commands/bundle-harmony.js file, import the OpenHarmony bundle configuration throughcreateHarmonyMetroConfig, and compile the bundle file based on the configuration. Then, you need to add operations related tocopyAssets. Ensure that the path format of the image resource is the same as that of the image resource encoded in the bundle.
How Do I Load a Bundle Only Once in RNApp Mode?
-
Symptom
When
RNAppis used to create a RN page, OpenHarmony and RN share the same RN container. However, each time a different RN page is displayed through the RN container, the bundle is reloaded. How can I load the bundle only once? How to shorten the loading time? -
Cause
RNApp automatically destroys
RNInstance. -
Solutions
-
Solution 1: Do not use RNApp because it will automatically destroy
RNInstance. You can useRNSurfaceandcreateAndRegisterRNInstanceto manageRNInstance. As long asRNInstanceexists, the bundle will not be reloaded. -
Solution 2: Push the bundle in the rawfile to the sandbox. Each time the page is loaded, you can determine whether the bundle exists in the sandbox. For details, see the
sandboxfile ofSampleAPP.
Note: When
RNSurfaceis used, theinit()method must be initialized afterrnInstancesCoordinator?.onWindowSizeChangeis created. Otherwise, thewindowsizechanges and theRNOHCoreContextis incorrect, which may cause text display problems.
How Do I Use Code to Refresh the RN Page?
When Metro is used to load the bundle of RN, you can forcibly reload the entire bundle according to the command in the menu. During development, if you want to reload the current RN bundle page after a service is complete, you can reload the bundle by referring to reload functions.
By default, the reload function of Metro is implemented for RNApp. If RNSurface is used to load the RN page during development, the reload function does not take effect. You can refer to the reload implementation logic in RNApp to implement the custom reload function.
The following uses the source code of RNApp as an example to describe the implementation logic of reload.
-
Bind the
shouldShowparameter.As a declarative
UIframework, ArkUI determines the creation, destruction, and update of theUIcomponent through state binding. The entry component of the RN page isRNSurface. To refresh the page, you can use theshouldShowvariable decorated by@Stateand determine the display and destruction ofRNSurfacethrough if judgment.build() { Stack() { if (this.shouldShow) { RNSurface({ ctx: this.ctx, surfaceConfig: { initialProps: this.initialProps ?? {}, appKey: this.appKey, } as SurfaceConfig2, }) } } }To refresh the RN page, set
shouldShowtofalse, reload the bundle, and resetshouldShowtotrue.private async cleanUp() { const stopTracing = this.logger.clone("cleanUp").startTracing() this.shouldShow = false ... } -
Clear the RNInstance.
RNOH provides the method of clearing
RNInstance.this.rnohCoreContext!.destroyAndUnregisterRNInstance(this.rnInstance)calls theonDestroymethod ofRNInstanceand clearsRNInstancefromRNInstanceRegistry.private async cleanUp() { const stopTracing = this.logger.clone("cleanUp").startTracing() this.shouldShow = false if (this.shouldDestroyRNInstance) { this.rnohCoreContext!.destroyAndUnregisterRNInstance(this.rnInstance) } ... } -
Re-create the RNInstance and load the bundle.
The RELOAD operation of
RNAppis performed by subscribing to the RELOAD event of devToolsController. After theRNInstanceis cleared, theinitmethod is immediately called to create aRNInstanceand execute therunJSBundle.this.cleanUpCallbacks.push(this.ctx.devToolsController.eventEmitter.subscribe("RELOAD", async () => { await this.cleanUp() this.init() })) -
Reload the page.
You need to re-create the
RNSurfaceafter the bundle is loaded. In the preceding steps,shouldShowhas been set tofalse. In this case,shouldShowis set totrueand a newRNSurfacecomponent will be created by ArkUI.
Does the Current OpenHarmony RN Support the Migration of Native Modules of the Old Architecture to Turbo Modules?
-
Q: Does the current OpenHarmony RN support the migration of native modules of the old architecture to turbo modules?
-
A: OpenHarmony RN is developed based on the new RN architecture. The native module (adapted to Android and iOS) developed based on the old RN architecture cannot be migrated to the turbo module of the new RN architecture. They are incompatible at the bottom layer.
How to Use Custom Fonts?
-
Symptom
In ArkUI, after you perform custom font registration, the fontFamily setting in the RN code does not take effect.
-
Cause
RN components are loaded to the native container through XComponent. You need to register the font in the CAPI code. RNOH specifies a font registration entry. You need to transfer custom font data to RN.
-
Solutions
(1) Add a font file.
Same as the image file, the RN font file is stored in the
resources/rawfile/directory. To distinguish the two files, you are advised to create a fonts folder in the assets directory, as shown in the following figure.
(2) Configure the font.
In the previous step, an **SFMono-Bold.ttf** font file is added to the project. Then you need to create a `Record` object.const fontsFamily: Record<string, Resource | string> = { 'SFMono': $rawfile('fonts/SFMono-Bold.ttf'), }(3) Register the font.
RNOH provides
fontOptionsin theRNInstancebuild parameters for custom font configuration.this.rnInstance = await rnohCoreContext.createAndRegisterRNInstance({ createRNPackages: createRNPackages, enableNDKTextMeasuring: true, enableBackgroundExecutor: true, enableCAPIArchitecture: ENABLE_CAPI_ARCHITECTURE, enableDebugger: true, arkTsComponentNames: arkTsComponentNames, fontResourceByFontFamily: fontsFamily });If
RNAppis used, set this parameter inRNInstanceConfig:RNApp({ rnInstanceConfig: { createRNPackages, enableNDKTextMeasuring: true, enableBackgroundExecutor: true, enableCAPIArchitecture: true, arkTsComponentNames: arkTsComponentNames, fontResourceByFontFamily: fontsFamily }, }) ...(4) Specify the font family on the RN side.
The code on the RN side is the same as that on iOS and Android. Use fontFamily to specify the custom text font.
<Text style={{ fontFamily: "SFMono" }}> Hello World </Text>
Why Does the gestureEnabled:false Setting for the Navigation Page Do Not Take Effect?
-
Symptom
For RN, after the
react-navigationlibrary is used to setgestureEnabled: falseof a subpage, you can still swipe back to the previous page.<NavigationContainer ref={navigationRef}> <Stack.Navigator initialRouteName="Page1"> <Stack.Screen name="Page1" component={Page1} /> <Stack.Screen name="Page2" component={Page2} options={{ gestureEnabled: false, }}/> </Stack.Navigator> </NavigationContainer> -
Cause
The
@react-navigation/stacklibrary depends on thereact-native-screenslibrary. You need to call the native method throughreact-native-screensto disable the back gesture. The following describes the native code for disabling the gesture on iOS.
The
react-native-screenslibrary is now incompatible with OpenHarmony. Therefore, settinggestureEnabled:falseis invalid. -
Solution
Currently, you can use BackHanlder to listen for the back press event to achieve the same effect.
For example, there are two pages in the navigation route stack: Home and Details. After you press Home to go to Details, you are not allowed to swipe back. In this case, you can add a listener in Details and return
truein the listener callback, indicating that the default back behavior is disabled.// DetailsScreen.js import React, { useEffect } from 'react'; import { Button, View, BackHandler } from 'react-native'; function DetailsScreen({ route, navigation }) { useEffect(() => { const backHandlerListener = BackHandler.addEventListener('hardwareBackPress', () => { console.log('detail back') return true }) return () => { backHandlerListener.remove(); } }) return ( <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}> {/* <StatusBar backgroundColor={'blue'} hidden={false}/> */} <Button title="Go back" onPress={() => navigation.goBack()} /> </View> ); } export default DetailsScreen;
How to add RNOH dependencies in third-party libraries or custom modules
-
Q: Some third-party libraries or custom modules also depend on RNOH. How should they be configured?
-
A: Add the following dependency in the project-level
oh-package.json5:{ ... "overrides":{ "@rnoh/react-native-openharmony":"^X.X.X" } }Also add the following dependency in the custom module's
oh-package.json5:{ ... "dependencies": { "@rnoh/react-native-openharmony": "^X.X.X" } }