# RNFlutterH5 **Repository Path**: scenario-samples/rnflutter-h5 ## Basic Information - **Project Name**: RNFlutterH5 - **Description**: 【鸿蒙 Harmony Next 示例 代码】本示例主要演示如何实现RN、Flutter、H5三个框架的模块页面相互跳转的功能。 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-11-26 - **Last Updated**: 2025-11-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 实现在RN、Flutter、H5页面之间互相跳转的Demo ## 场景介绍 应用在开发过程中可能涉及多个模块分别使用不同的框架开发的module,例如部分module涉及RN、Flutter、H5,本示例主要演示如何实现三个框架的模块页面相互跳转的功能。 ## 效果图预览 ## 实现思路 - RN实现:RN侧通过TurboModule实现RN与ArkTS通信,进行页面路由跳转。 - RN页面示例代码: ```tsx { SampleTurboModule.pushStringToHarmony('pages/FlutterPages', 1); }} /> { SampleTurboModule.pushStringToHarmony('pages/RNPage', 1); }} /> { SampleTurboModule.pushStringToHarmony('pages/H5Page', 1); }} /> ``` - RN侧TurboModule示例代码: ```tsx // 使用自定义的TurboModules export interface SpecSampleTurboModule extends TurboModule { pushStringToHarmony(arg: string, id?: number): string; } export default TurboModuleRegistry.getEnforcing('SampleTurboModule'); ``` - ArkTS侧TurboModule示例代码: ```TS export class SampleTurboModule extends TurboModule { public static readonly NAME = 'SampleTurboModule'; pushStringToHarmony(arg: string, id?: number): string { if (id) { let data: TurboModuleEventData = { param: arg } emitter.emit({ eventId: id }, { data: data }) } return arg; } } ``` - Flutter实现:Flutter使用MethodChannel实现与ArkTS通信,通过emitter发送通知到主页面,实现路由跳转到RN和H5页面。 - Flutter页面需要声明MethodChannel对象,按钮点击触发Flutter调用ArkTS方法,示例代码如下: ```dart import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: '居中按钮示例', theme: ThemeData( primarySwatch: Colors.blue, ), home: CenterButtonPage(), ); } } class CenterButtonPage extends StatelessWidget { final _platform = const MethodChannel('samples.flutter.dev/routerRN'); const CenterButtonPage({super.key}); void _handleRNButtonClick() async { await _platform.invokeMethod('routerRN'); } void _handleH5ButtonClick() async { await _platform.invokeMethod('routerH5'); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Flutter页面'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( onPressed: _handleRNButtonClick, child: Text('点击我跳转RN页面'), style: ElevatedButton.styleFrom( padding: EdgeInsets.symmetric(horizontal: 30, vertical: 15), textStyle: TextStyle(fontSize: 20), ), ), SizedBox(height: 20), // 添加一些间距 ElevatedButton( onPressed: _handleH5ButtonClick, child: Text('点击我跳转到H5'), style: ElevatedButton.styleFrom( padding: EdgeInsets.symmetric(horizontal: 30, vertical: 15), textStyle: TextStyle(fontSize: 20), ), ), ], ), ), ); } } ``` - MethodChannel代码示例: ```TypeScript export default class RouterPlugin implements FlutterPlugin, AbilityAware { private channel?: MethodChannel; private api = new RouterApi(); onAttachedToAbility(binding: AbilityPluginBinding): void { Log.i(TAG, "onAttachedToAbility") } onDetachedFromAbility(): void { Log.i(TAG, "onDetachedFromAbility") } onAttachedToEngine(binding: FlutterPluginBinding): void { Log.i(TAG, "onAttachedToEngine"); this.channel = new MethodChannel(binding.getBinaryMessenger(), "samples.flutter.dev/routerRN"); let that = this; this.channel.setMethodCallHandler({ onMethodCall(call: MethodCall, result: MethodResult) { switch (call.method) { case "routerRN": that.api.routerRN(result); break; case "routerH5": that.api.routerH5(result); break; } } }) } onDetachedFromEngine(binding: FlutterPluginBinding): void { Log.i(TAG, "onDetachedFromEngine"); this.channel?.setMethodCallHandler(null); } getUniqueClassName(): string { return "RouterPlugin"; } } class RouterApi { routerH5(result: MethodResult) { // 发送消息跳转页面 emitter.emit({ eventId: 1 }, { data: {param:"pages/H5Page"} }) result.success(null); } routerRN(result: MethodResult) { // 发送消息跳转页面 emitter.emit({ eventId: 1 }, { data: {param:"pages/RNPage"} }) result.success(null); } } ``` - FlutterEntry示例代码: ```TypeScript export class NavFlutterEntry extends FlutterEntry { pathStack?: NavPathStack; constructor(context: Context, params: Record = {}, pathStack?: NavPathStack) { super(context, params) this.pathStack = pathStack; } popSystemNavigator(): boolean { if (this.pathStack) { this.pathStack.pop(); return true; } return false; } configureFlutterEngine(flutterEngine: FlutterEngine): void { super.configureFlutterEngine(flutterEngine); GeneratedPluginRegistrant.registerWith(flutterEngine); this.delegate?.addPlugin(new RouterPlugin()); } } ``` - EntryAbility中需要在对应生命周期内调用FlutterManager的pushUIAbility和pushWindowStage方法,示例代码如下: ```TypeScript export default class EntryAbility extends RNAbility implements ExclusiveAppComponent{ detachFromFlutterEngine(): void { } getAppComponent(): UIAbility { return this; } getPagePath() { return 'pages/Index'; } onCreate(want: Want): void { FlutterManager.getInstance().pushUIAbility(this); super.onCreate(want) } onDestroy(): void | Promise { FlutterManager.getInstance().popUIAbility(this); } onWindowStageCreate(windowStage: window.WindowStage) { FlutterManager.getInstance().pushWindowStage(this, windowStage); super.onWindowStageCreate(windowStage) try { let options: dataPreferences.Options = { name: 'test' }; preferences = dataPreferences.getPreferencesSync(this.context, options) } catch (err) { console.error('Failed to get preferences') } AppStorage.setOrCreate('RNAbility', this); } onWindowStageDestroy() { FlutterManager.getInstance().popWindowStage(this); } } ``` - H5实现:H5新增button,跳转RN和Flutter页面,H5使用JsBridge调用ArkTS方法。 - H5示例代码: ```Html Input Example
``` - JsBridge使用emitter发送消息,参数分别为FlutterPages、RNPage,由主页面接收后实现路由跳转,示例代码如下: ```TypeScript class JsBridge { private uiContext: UIContext constructor(uiContext: UIContext) { this.uiContext = uiContext } routeFlutter() { emitter.emit({ eventId: 1 }, { data: {param:"pages/FlutterPages"} }) } routeRN() { emitter.emit({ eventId: 1 }, { data: {param:"pages/RNPage"} }) } } @Component export struct H5Page { uiContext?: UIContext webController: webview.WebviewController = new webview.WebviewController(); @State js_bridge: JsBridge | null = null; @Consume('pagePathsGoods') pagePathsGoods: NavPathStack; aboutToAppear() { this.uiContext = this.getUIContext(); this.js_bridge = new JsBridge(this.uiContext) } aboutToDisappear(): void { this.webController.deleteJavaScriptRegister("JsBridge"); } build() { NavDestination() { Column() { Web({ src: $rawfile('test.html'), controller: this.webController }) // 将对象注入到web端 .javaScriptProxy({ object: this.js_bridge, name: "JsBridge", methodList: ["routeFlutter","routeRN"], controller: this.webController }) } .margin({top:50}) .width('100%') .height('100%') } .title("H5页面") .hideTitleBar(true) } } ``` - ArkUI实现:ArkTS中使用emitter接收从Flutter、RN、H5中跳转页面的通知,通过Navigation进行页面路由跳转,默认展示RN页面,示例代码: ```TypeScript @Builder PageMap(name: string, param: string) { if (name === 'FlutterPages') { FlutterPages() } else if (name === 'RNPage') { RNPage() } else if (name === 'H5Page') { H5Page() } } aboutToAppear() { emitter.on({ eventId: 1 }, (eventData) => { if (eventData.data) { console.log('[TurboModule] param = ' + eventData.data['param']); const path: string = eventData.data['param'] if (path === "pages/FlutterPages") { this.pagePathsGoods.pushPath({ name: "FlutterPages" }) } else if (path === "pages/RNPage") { this.pagePathsGoods.pushPath({ name: "RNPage" }) } else if (path === "pages/H5Page") { this.pagePathsGoods.pushPath({ name: "H5Page" }) } } }); } build() { Navigation(this.pagePathsGoods) { if (this.isMetroAvailable) { MetroBaseRN({ moduleName: this.moduleName, }).align(Alignment.Top).margin({ top: 0 }) } else if (this.instance) { BaseRN({ rnInstance: this.instance, moduleName: this.moduleName, bundlePath: this.bundlePath, }).align(Alignment.Top).margin({ top: 0 }) } } .navDestination(this.PageMap) .hideTitleBar(true) .hideToolBar(true) } ``` ## 使用说明 - 前提条件:本地完成Flutter环境搭建、RN环境搭建,本地已创建RN工程和Flutter module。 - 本示例以RN工程为主工程,Flutter_module以har包形式引入,H5在harmony工程内。 - 运行demo流程: 1. 在fluttermodule中执行flutter build har ,将.ohos中的har包复制到harmony/har目录下。 2. 在RNProject工程中,先执行npm install,再执行npm run codegen,再执行npm run dev:all。 3. 使用Deveco Studio打开harmony工程,签名运行到真机即可。 ## 约束与限制 - 本示例支持API Version 17 Release及以上版本。 - 本示例支持HarmonyOS 5.0.5 Release SDK及以上版本。 - 本示例需要使用DevEco Studio 5.0.5 Release及以上版本进行编译运行。 ## 工程结构&模块类型 ``` |────flutter_module //flutter module | |──lib | | |──main.dart // flutter页面 | |──pubspec.yam //flutter依赖配置 | |────RNProject // RN主工程 | |──harmony // harmonyOS主工程 | |────entry // ets代码 | |────src | |──────bundles //RN 模块代码 | |────────Goods //RN页面 ``` ## 参考文档 [RN开发文档](https://gitcode.com/openharmony-sig/ohos_react_native/tree/0.72.5-ohos-5.0-release/docs/zh-cn) [RN TurboModule](https://gitcode.com/openharmony-sig/ohos_react_native/blob/0.72.5-ohos-5.0-release/docs/zh-cn/TurboModule.md) [Flutter开发文档](https://gitcode.com/openharmony-sig/flutter_samples/tree/master/ohos/docs) [Flutter MethodChannel](https://gitcode.com/openharmony-sig/flutter_samples/blob/master/ohos/docs/04_development/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8Flutter%E4%B8%8EOpenHarmony%E9%80%9A%E4%BF%A1%20FlutterChannel.md) [H5页面调用应用侧函数](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/web-in-page-app-function-invoking) ## ChangeLog:修改日志 | 修改内容 | 时间 | |-------|---------| | 第一次提交 | 2025.09 |