Harmony App 开发中Flutter 与鸿蒙原生交互传参教程

Flutter 与鸿蒙原生交互传参教程

本教程将手把手教你实现 Flutter 向鸿蒙原生传递参数,并接收返回结果。

效果图

最终效果

  • Flutter 页面有两个输入框,输入两个数字
  • 点击按钮调用鸿蒙原生方法
  • 鸿蒙原生计算两数之和
  • 将结果返回给 Flutter 页面显示

项目结构

复制代码
项目根目录/
├── lib/
│   └── pages/
│       └── Demo/
│           └── index.dart              ← Flutter 页面(步骤1)
└── ohos/
    └── entry/
        └── src/
            └── main/
                └── ets/
                    ├── entryability/
                    │   └── EntryAbility.ets    ← 注册插件(步骤3)
                    └── plugins/
                        └── NativePlugin.ets    ← 鸿蒙原生插件(步骤2)

步骤 1:创建 Flutter 页面

1.1 新建文件夹

lib/pages/ 目录下新建 Demo 文件夹。

1.2 创建 index.dart

Demo 文件夹中新建 index.dart 文件,写入以下代码:

dart 复制代码
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

/// Demo 页面 - Flutter 与鸿蒙原生交互:传参计算
class DemoPage extends StatefulWidget {
  const DemoPage({super.key});

  @override
  State<DemoPage> createState() => _DemoPageState();
}

class _DemoPageState extends State<DemoPage> {
  // MethodChannel 名称需要与鸿蒙端一致
  static const platform = MethodChannel('com.example.native/channel');

  final TextEditingController _num1Controller = TextEditingController();
  final TextEditingController _num2Controller = TextEditingController();
  String _result = '';
  bool _isLoading = false;

  // 调用鸿蒙原生方法进行计算
  Future<void> _calculate() async {
    final num1 = int.tryParse(_num1Controller.text) ?? 0;
    final num2 = int.tryParse(_num2Controller.text) ?? 0;

    setState(() {
      _isLoading = true;
      _result = '';
    });

    try {
      // 调用鸿蒙原生的 calculate 方法,传入两个数字
      final result = await platform.invokeMethod('calculate', {
        'num1': num1,
        'num2': num2,
      });
      setState(() => _result = '计算结果: $result');
    } on PlatformException catch (e) {
      setState(() => _result = '调用失败: ${e.message}');
    } on MissingPluginException {
      setState(() => _result = '插件未注册,请检查鸿蒙端代码');
    } finally {
      setState(() => _isLoading = false);
    }
  }

  @override
  void dispose() {
    _num1Controller.dispose();
    _num2Controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter ↔ 鸿蒙 计算器'),
        backgroundColor: Colors.green,
        foregroundColor: Colors.white,
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // 说明卡片
            _buildInfoCard(),
            const SizedBox(height: 20),

            // 输入区域
            _buildInputSection(),
            const SizedBox(height: 20),

            // 计算按钮
            ElevatedButton(
              onPressed: _isLoading ? null : _calculate,
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.green,
                padding: const EdgeInsets.symmetric(vertical: 16),
              ),
              child: _isLoading
                  ? const SizedBox(
                      height: 20,
                      width: 20,
                      child: CircularProgressIndicator(
                        strokeWidth: 2,
                        color: Colors.white,
                      ),
                    )
                  : const Text(
                      '调用鸿蒙原生计算',
                      style: TextStyle(fontSize: 16, color: Colors.white),
                    ),
            ),
            const SizedBox(height: 20),

            // 结果显示
            _buildResultCard(),
          ],
        ),
      ),
    );
  }

  Widget _buildInfoCard() {
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Colors.blue[50],
        borderRadius: BorderRadius.circular(12),
        border: Border.all(color: Colors.blue[200]!),
      ),
      child: const Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text('📱 Flutter 与鸿蒙原生交互示例',
              style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
          SizedBox(height: 8),
          Text('1. 在下方输入两个数字'),
          Text('2. 点击按钮调用鸿蒙原生方法'),
          Text('3. 鸿蒙端计算后返回结果'),
        ],
      ),
    );
  }

  Widget _buildInputSection() {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Text('输入两个数字',
                style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
            const SizedBox(height: 16),
            Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: _num1Controller,
                    keyboardType: TextInputType.number,
                    decoration: const InputDecoration(
                      labelText: '数字 1',
                      border: OutlineInputBorder(),
                      hintText: '请输入第一个数字',
                    ),
                  ),
                ),
                const Padding(
                  padding: EdgeInsets.symmetric(horizontal: 16),
                  child: Text('+', style: TextStyle(fontSize: 24)),
                ),
                Expanded(
                  child: TextField(
                    controller: _num2Controller,
                    keyboardType: TextInputType.number,
                    decoration: const InputDecoration(
                      labelText: '数字 2',
                      border: OutlineInputBorder(),
                      hintText: '请输入第二个数字',
                    ),
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildResultCard() {
    return Container(
      padding: const EdgeInsets.all(20),
      decoration: BoxDecoration(
        color: _result.contains('失败') ? Colors.red[50] : Colors.green[50],
        borderRadius: BorderRadius.circular(12),
        border: Border.all(
          color: _result.contains('失败') ? Colors.red[200]! : Colors.green[200]!,
        ),
      ),
      child: Column(
        children: [
          const Text('鸿蒙原生返回结果',
              style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14, color: Colors.grey)),
          const SizedBox(height: 8),
          Text(
            _result.isEmpty ? '等待计算...' : _result,
            style: TextStyle(
              fontSize: 24,
              fontWeight: FontWeight.bold,
              color: _result.contains('失败') ? Colors.red : Colors.green[700],
            ),
          ),
        ],
      ),
    );
  }
}

代码说明

代码 作用
MethodChannel('com.example.native/channel') 创建通信通道,名称需与鸿蒙端一致
platform.invokeMethod('calculate', {...}) 调用鸿蒙原生方法,传递参数
{'num1': num1, 'num2': num2} 传递给鸿蒙端的参数(Map 格式)

步骤 2:创建鸿蒙原生插件

2.1 新建文件夹

ohos/entry/src/main/ets/ 目录下新建 plugins 文件夹(如果不存在)。

2.2 创建 NativePlugin.ets

plugins 文件夹中新建 NativePlugin.ets 文件,写入以下代码:

typescript 复制代码
import { FlutterEngine, MethodChannel, MethodCall, MethodResult, MethodCallHandler } from '@ohos/flutter_ohos';

/**
 * 原生交互插件
 */
export class NativePlugin implements MethodCallHandler {
  private channel: MethodChannel | null = null;

  constructor() {}

  // 注册到 FlutterEngine
  onAttachedToEngine(flutterEngine: FlutterEngine): void {
    // 创建 MethodChannel,名称需要与 Flutter 端一致
    this.channel = new MethodChannel(
      flutterEngine.dartExecutor.getBinaryMessenger(),
      'com.example.native/channel'
    );
    // 设置方法调用处理器
    this.channel.setMethodCallHandler(this);
  }

  // 处理 Flutter 端的方法调用
  onMethodCall(call: MethodCall, result: MethodResult): void {
    switch (call.method) {
      case 'calculate':
        // 从 Map 中获取参数(重要:Flutter 传递的 Map 需要用这种方式获取)
        const argsMap = call.args as Map<string, Object>;
        const n1 = argsMap.get('num1') as number || 0;
        const n2 = argsMap.get('num2') as number || 0;
        // 计算两数之和
        const sum = n1 + n2;
        // 打印日志
        console.info('[NativePlugin] Calculate: ' + n1 + ' + ' + n2 + ' = ' + sum);
        // 返回结果给 Flutter
        result.success(sum);
        break;
      default:
        result.notImplemented();
        break;
    }
  }

  // 从 FlutterEngine 解绑
  onDetachedFromEngine(): void {
    if (this.channel) {
      this.channel.setMethodCallHandler(null);
      this.channel = null;
    }
  }
}

代码说明

代码 作用
call.args as Map<string, Object> Flutter 传递的 Map 参数需要转换为 Map 类型
argsMap.get('num1') 通过 key 获取 Map 中的值
MethodChannel(..., 'com.example.native/channel') 创建通道,名称必须与 Flutter 端一致
call.method 获取 Flutter 调用的方法名
result.success(sum) 返回结果给 Flutter

重要提示 :Flutter 传递的 Map 参数在鸿蒙端不能直接用 interface 接收,必须用 Map<string, Object> 类型,然后通过 .get('key') 获取值。


步骤 3:注册插件

3.1 修改 EntryAbility.ets

打开 ohos/entry/src/main/ets/entryability/EntryAbility.ets,修改为:

typescript 复制代码
import { FlutterAbility, FlutterEngine } from '@ohos/flutter_ohos';
import { GeneratedPluginRegistrant } from '../plugins/GeneratedPluginRegistrant';
import { NativePlugin } from '../plugins/NativePlugin';

export default class EntryAbility extends FlutterAbility {
  configureFlutterEngine(flutterEngine: FlutterEngine) {
    super.configureFlutterEngine(flutterEngine);
    GeneratedPluginRegistrant.registerWith(flutterEngine);

    // 注册自定义原生插件
    const nativePlugin = new NativePlugin();
    nativePlugin.onAttachedToEngine(flutterEngine);
  }
}

代码说明

  • import { NativePlugin } - 导入我们创建的插件
  • nativePlugin.onAttachedToEngine(flutterEngine) - 将插件注册到 FlutterEngine

步骤 4:在 main.dart 中添加页面

4.1 导入页面

lib/main.dart 顶部添加导入:

dart 复制代码
import 'package:lesson77/pages/Demo/index.dart';

4.2 添加到页面列表

_page 列表中添加:

dart 复制代码
final List<Widget> _page = const[
  // ... 其他页面
  DemoPage(),
];

4.3 添加底部导航项

_navBarItems 列表中添加:

dart 复制代码
const BottomNavigationBarItem(
  icon: Icon(Icons.code_outlined),
  activeIcon: Icon(Icons.code, color: Colors.blue),
  label: 'demo',
),

4.4 更新标题

_getAppBarTitle() 方法中添加:

dart 复制代码
case 5: return 'Demo';  // 根据实际索引调整

步骤 5:构建运行

5.1 清理项目

bash 复制代码
flutter clean

5.2 运行项目

bash 复制代码
flutter run

5.3 测试

  1. 切换到 Demo 页面
  2. 在两个输入框中输入数字(如 10 和 20)
  3. 点击"调用鸿蒙原生计算"按钮
  4. 查看返回结果(应显示 30)

交互流程图

复制代码
┌─────────────────────────────────────────────────────────────┐
│                      Flutter 端                              │
│  ┌─────────────────────────────────────────────────────┐    │
│  │  输入: num1 = 10, num2 = 20                          │    │
│  │  调用: platform.invokeMethod('calculate', {...})     │    │
│  └─────────────────────────────────────────────────────┘    │
└─────────────────────────┬───────────────────────────────────┘
                          │
                          ▼ MethodChannel
┌─────────────────────────────────────────────────────────────┐
│                      鸿蒙端                                  │
│  ┌─────────────────────────────────────────────────────┐    │
│  │  接收: call.method = 'calculate'                     │    │
│  │  参数: call.args = {num1: 10, num2: 20}              │    │
│  │  计算: sum = 10 + 20 = 30                            │    │
│  │  返回: result.success(30)                            │    │
│  └─────────────────────────────────────────────────────┘    │
└─────────────────────────┬───────────────────────────────────┘
                          │
                          ▼ 返回结果
┌─────────────────────────────────────────────────────────────┐
│                      Flutter 端                              │
│  ┌─────────────────────────────────────────────────────┐    │
│  │  接收: result = 30                                   │    │
│  │  显示: "计算结果: 30"                                 │    │
│  └─────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────┘

常见问题

Q1: 显示"插件未注册"

原因:MethodChannel 名称不匹配或插件未注册。

解决

  1. 检查 Flutter 端和鸿蒙端的 channel 名称是否一致
  2. 确保在 EntryAbility.ets 中注册了插件

Q2: 计算结果为 0

原因:参数获取方式错误。

解决 :Flutter 传递的 Map 参数在鸿蒙端必须用 Map<string, Object> 类型接收,然后通过 .get('key') 获取值:

typescript 复制代码
const argsMap = call.args as Map<string, Object>;
const n1 = argsMap.get('num1') as number || 0;
const n2 = argsMap.get('num2') as number || 0;

注意 :不能直接用 interface 接收,如 call.args as CalcArgs 会导致参数为 undefined。

Q3: 编译报错 "Object literal must correspond to some explicitly declared class or interface"

原因:ArkTS 要求对象字面量必须有对应的 interface。

解决 :这个错误通常出现在创建对象时,需要定义 interface。但对于 Flutter 传递的参数,使用 Map<string, Object> 即可。

Q4: 热重载不生效

原因:修改了鸿蒙端代码。

解决 :执行 flutter clean 后重新 flutter run


扩展练习

练习 1:实现减法

在鸿蒙端添加 subtract 方法:

typescript 复制代码
case 'subtract':
  const subArgs = call.args as CalcArgs;
  const diff = (subArgs.num1 || 0) - (subArgs.num2 || 0);
  result.success(diff);
  break;

练习 2:实现乘法和除法

自行实现 multiplydivide 方法。

练习 3:返回复杂对象

鸿蒙端返回 JSON 字符串,Flutter 端解析:

typescript 复制代码
// 鸿蒙端
result.success(JSON.stringify({sum: 30, product: 200}));

// Flutter 端
final data = json.decode(result);

总结

步骤 文件 操作
1 lib/pages/Demo/index.dart 新建 Flutter 页面,使用 MethodChannel 调用原生
2 ohos/.../plugins/NativePlugin.ets 新建鸿蒙插件,处理方法调用
3 ohos/.../entryability/EntryAbility.ets 修改,注册插件
4 lib/main.dart 修改,添加页面和导航
5 终端 执行 flutter cleanflutter run

通过 MethodChannel,你可以实现 Flutter 与鸿蒙原生的任意数据交互!

源代码

FlutterDemo

相关推荐
小白阿龙2 小时前
鸿蒙+flutter 跨平台开发——Text控件
flutter·鸿蒙
世人万千丶2 小时前
鸿蒙跨端框架 Flutter 学习 Day 3:综合实践——多维数据流与实时交互实验室
学习·flutter·华为·交互·harmonyos·鸿蒙
世人万千丶2 小时前
鸿蒙跨端框架 Flutter 学习 Day 3:工程实践——数据模型化:从黑盒 Map 走向强类型 Class
学习·flutter·ui·华为·harmonyos·鸿蒙·鸿蒙系统
IT陈图图3 小时前
基于 Flutter × OpenHarmony 的应用头部信息区域的实现与解析
flutter·华为·openharmony
IT陈图图4 小时前
基于 Flutter × OpenHarmony 的正则表达式测试器开发实战
flutter·正则表达式·openharmony
安卓理事人4 小时前
鸿蒙的“官方推荐”架构MVVM
华为·架构·harmonyos
小雨青年4 小时前
鸿蒙 HarmonyOS 6 | 逻辑核心 (06):本地 关系型数据库 (RDB) 的 CRUD 与事务处理
数据库·华为·harmonyos
低调小一4 小时前
Kotlin Multiplatform + 声明式 UI 三端实战:从工程结构到鸿蒙适配
ui·kotlin·harmonyos
小白阿龙4 小时前
鸿蒙+flutter 跨平台开发——鸿蒙版多功能计算器
flutter·华为·harmonyos