Flutter 三方库「flutter_web_auth_2」的鸿蒙化适配指南
开发工具: 华为云码道
本文配套仓库: oh-flutter/flutter_web_auth_2
flutter_web_auth_2 将浏览器认证与回调接收封装为 Flutter 插件,应用可以打开认证页面,并在服务重定向后取得回调 URL。本文以 flutter_web_auth_2 6.0.0-alpha.7 为例,介绍源码准备、OHOS 工程配置、ArkTS 实现和 example 真机运行。
插件声明 Android、iOS、macOS、Linux、Windows、Web 和 OHOS 平台实现,具体浏览器与会话能力随平台不同。配套仓库地址为 oh-flutter/flutter_web_auth_2。
一、插件简介与适配目标
浏览器认证让用户在浏览器中完成登录或授权,再通过约定的回调地址返回应用。OHOS 实现使用 UIAbilityContext.openLink 打开认证地址,通过 onNewWant 接收回调。
例如,账号登录、OAuth 授权或第三方服务绑定,可以沿用 FlutterWebAuth2.authenticate 的公开接口。插件负责浏览器跳转与回调传递,业务仍需按认证协议校验 state、处理授权码及服务端返回的错误。
认证调用返回 Future<String>。它在匹配回调到达时返回完整 URL,打开浏览器成功本身不会完成认证。
二、环境准备
环境搭建参考社区文档:Flutter OH 开发环境搭建,完成 Flutter OH SDK 安装、环境变量和 DevEco Studio 配置。
完成后,在宿主机终端执行以下命令,确认当前选中的是支持 OHOS 的 Flutter 工具链,并能发现目标设备:
flutter --version
flutter doctor -v
hdc list targets
工程使用的工具链和 SDK 配置如下:
| 项目 | 版本或配置 | 用途 |
|---|---|---|
| Flutter OHOS SDK | 3.44.9+ohos-0.0.1-canary1 | Flutter 编译与 OHOS 平台工具链 |
| Flutter 分支 | oh-3.44.9-dev | CPF-Flutter 对应开发分支 |
| Dart SDK | 3.12.2 | Dart 语言与包管理环境 |
| HarmonyOS 开发套件 | 7.0.0(API 26) | 开发套件版本及对应的 API 级别 |
compileSdkVersion | 26.0.0 | 编译时使用的 SDK API |
targetSdkVersion | 26.0.0 | 应用面向的行为版本 |
compatibleSdkVersion | 5.1.0(18) | 当前工程声明的最低兼容版本 |
| 插件版本 | 6.0.0-alpha.7 | pubspec.yaml 中的包版本 |
| 原生语言 | ArkTS | HarmonyOS 插件实现 |
| 插件产物 | HAR | 被应用 entry 模块依赖 |
2.1 开发套件版本与工程中的 SDK 版本配置
7.0.0(API 26) 和 26.0.0 涉及 HarmonyOS 开发套件版本(API 版本)及其底座 OpenHarmony 的版本号体系。在本文工程中,相关版本的含义与配置方式如下:
7.0.0(API 26)表示 HarmonyOS 开发套件版本为7.0.0,对应 API 26。26.0.0是本文 HarmonyOS 应用工程中compileSdkVersion和targetSdkVersion的属性值。5.1.0(18)是本文工程中compatibleSdkVersion的属性值,声明最低兼容 API 18。
对应的 product 配置为:
{
"name": "default",
"compatibleSdkVersion": "5.1.0(18)",
"compileSdkVersion": "26.0.0",
"targetSdkVersion": "26.0.0",
"runtimeOS": "HarmonyOS"
}
这组配置使用 API 26 SDK 编译,并以 API 26 为目标版本,最低兼容 API 18。自定义 Scheme 必须与宿主 Ability 清单一致,并有浏览器或应用能够处理认证地址。OHOS 当前按 Scheme 匹配回调,不替业务验证 OAuth state 或认证结果。
三、从源码仓库开始准备适配工程
3.1 将上游源码同步到 AtomGit
适配已有第三方插件时,先从 Pub 包信息或项目 README 确认上游地址、包名、版本和许可证。同步仓库时保留源码、许可证和提交历史,方便后续跟踪上游更新。
在 AtomGit 网页的新建仓库流程中,找到从已有仓库导入的入口,填写上游 Git 地址,选择自己有写权限的组织或个人空间,设置目标仓库名称后执行导入。完成后检查目标分支、pubspec.yaml、LICENSE 和提交历史是否完整,并记录适配起点的提交号。如果上游本身托管在 AtomGit,也可以通过 Fork 获得自己的工作仓库。
上游源码为 https://github.com/ThexXTURBOXx/flutter_web_auth_2,本文基于 6.0.0-alpha.7。配套仓库为 flutter_web_auth_2。需要提交修改时,使用自己有写权限的仓库或 Fork。
3.2 将代码拉取到宿主机
在安装了 Flutter OH 和 DevEco Studio 的开发电脑上打开终端,进入准备存放项目的目录,执行:
git clone https://atomgit.com/oh-flutter/flutter_web_auth_2.git
cd flutter_web_auth_2
pwd
ls
git remote -v
git status --short --branch
git rev-parse HEAD
git clone 会创建 flutter_web_auth_2/ 仓库目录。目标 Dart 包位于 flutter_web_auth_2/;以下章节中,“插件根目录”指该包目录,其中应能看到 pubspec.yaml、lib/ 和 example/。Git 仓库名和 Dart 包名均为 flutter_web_auth_2。
需要使用与本文相同的代码版本时,先确认配套仓库已经包含下列本地参考提交,再在没有未提交修改的仓库中执行:
git switch --detach 47153637dea216732cb7ae071224669d189a9b27
适配其他插件时,使用该插件对应版本的 tag 或 commit 作为分支起点。

3.3 在仓库根目录创建适配分支
接着在 flutter_web_auth_2/ 根目录创建分支。分支名统一使用 feat/ohos_库名称_版本号,库名称取 flutter_web_auth_2/pubspec.yaml 的 name,版本号取此次适配的基线版本。本例为:
git switch -c feat/ohos_flutter_web_auth_2_6.0.0-alpha.7
git branch --show-current
如果该分支已存在,使用 git switch feat/ohos_flutter_web_auth_2_6.0.0-alpha.7 切换即可。

3.4 自动补全 OHOS 适配结构
分支在 Git 仓库根目录创建;随后执行 cd flutter_web_auth_2 进入插件包根目录,再执行结构补全。以下命令适用于尚无 ohos/ 目录的既有 Flutter 平台插件:
flutter create --template=plugin --platforms=ohos --project-name flutter_web_auth_2 .
git status --short
git diff -- pubspec.yaml lib example
--template=plugin指定插件模板。--platforms=ohos指定需要补全的平台。--project-name flutter_web_auth_2使用 Dart 包名,避免当前目录重命名后生成错误的包名。- 最后的
.表示在当前插件目录补全工程,不是另建一层flutter_web_auth_2/。
该命令生成 OHOS 平台脚手架,业务逻辑需要在 ArkTS 中实现。生成后通过 diff 检查 pubspec.yaml、lib/ 和 example/ 的变化,保留已有 API、其他平台注册项及依赖配置。不同 Flutter OH 版本生成的模板可能略有差异。
如果生成后 example/ohos/ 仍不存在,进入已有示例应用补全平台:
cd example
flutter create --platforms=ohos .
cd ..
本地适配工作区已经包含 ohos/ 和 example/ohos/,直接运行示例时可以跳过结构补全。新建插件则使用 flutter create --org com.nutpi --template=plugin --platforms=ohos flutter_web_auth_2;已有插件使用上面的 . 在当前目录补全。

3.5 适配后的项目目录
适配后的关键目录如下:
flutter_web_auth_2/flutter_web_auth_2/
├── lib/
│ ├── flutter_web_auth_2.dart
│ └── src/
│ └── options.dart
├── ohos/
│ ├── src/
│ │ └── main/
│ │ ├── ets/
│ │ │ └── components/
│ │ │ └── plugin/
│ │ │ └── FlutterWebAuth2Plugin.ets
│ │ └── module.json5
│ ├── index.ets
│ └── oh-package.json5
├── example/
│ ├── lib/
│ │ └── main.dart
│ └── ohos/
│ ├── entry/
│ │ └── src/
│ │ └── main/
│ │ └── module.json5
│ └── build-profile.json5
├── pubspec.yaml
├── README.OpenHarmony_CN.md
├── README.OpenHarmony.md
├── CHANGELOG.OpenHarmony.md
└── test/
项目根目录如下,其中包含 ohos/、example/ 及实际保留的说明文件;交付文档清单见第六节:

| 文件 | 主要职责 |
|---|---|
lib/flutter_web_auth_2.dart | 提供业务公开 API |
lib/src/options.dart | 实现平台协议或数据模型 |
ohos/src/main/ets/components/plugin/FlutterWebAuth2Plugin.ets | 注册通道并实现 OHOS 原生能力 |
ohos/src/main/module.json5 | 声明 HAR 模块和权限 |
example/lib/main.dart | 演示接口调用与结果显示 |
四、Dart 接口与通道分析
OHOS 实现需要遵循 Dart 层已有的方法、参数和回调约定。先阅读 lib/flutter_web_auth_2.dart、lib/src/options.dart,再在 ohos/src/main/ets/components/plugin/FlutterWebAuth2Plugin.ets 中实现对应的原生调用。适配其他已有平台的插件时,也应保留其公开接口和其他平台实现。
本例的对应关系如下:
| Dart 入口或模型 | 通道协议 | OHOS 实现 | 应保持的行为 |
|---|---|---|---|
FlutterWebAuth2.authenticate(...) | authenticate | openLink + onNewWant | 返回完整回调 URL |
| 恢复前台清理 | clearAllDanglingCalls | 取消待处理 Map | 未完成请求得到 CANCELED |
FlutterWebAuth2Options | options Map | 当前 OHOS 不读取该 Map | 不能承诺其他平台选项生效 |
原生端需要保持方法名、参数键和返回类型一致,不能只保留方法名称而改变业务语义。
4.1 跨端架构与调用时序
Flutter 侧通过 MethodChannel('flutter_web_auth_2') 调用 OHOS 实现。本插件使用一次性的异步调用,不提供持续事件流。
4.1.1 一次完整浏览器认证的时序
4.2 回调模型:URL 字符串与待处理请求
final uri = Uri.parse(callbackUrl);
final code = uri.queryParameters['code'];
final error = uri.queryParameters['error'];
上面是收到回调后的解析片段,callbackUrl 为 authenticate 返回值。原生 PendingAuthentication 保存 MethodResult,Map 的键是小写 Scheme。成功回调只是 URI 返回,不自动表示服务认证成功。
| 结果 | 含义 |
|---|---|
| 完整 URI | 匹配 Scheme 的回调 |
AUTH_IN_PROGRESS | 相同 Scheme 已有请求 |
CANCELED | 待处理认证被清理或 Ability 解绑 |
NO_BROWSER / NO_ABILITY | 无法打开地址或没有宿主 |
4.3 公开 API 与平台接口
业务只需调用静态 authenticate,callbackUrlScheme 需符合 Dart 正则校验。平台接口包位于仓库的 flutter_web_auth_2_platform_interface/,本地工作区共同解析该包。
static Future<String> authenticate({
required String url,
required String callbackUrlScheme,
FlutterWebAuth2Options options = const FlutterWebAuth2Options(),
}) async {
assert(
!(kIsWeb && options.debugOrigin != null && !kDebugMode),
'Do not use debugOrigin in production',
);
_assertCallbackScheme(callbackUrlScheme);
WidgetsBinding.instance.removeObserver(
_resumedObserver,
); // safety measure so we never add this observer twice
WidgetsBinding.instance.addObserver(_resumedObserver);
return _platform.authenticate(
url: url,
callbackUrlScheme: callbackUrlScheme,
options: options.toJson(),
);
}
OHOS 不处理 options Map,因此 preferEphemeral、intentFlags、customTabsPackageOrder 以及示例传入的 timeout 都不能被描述成已在 OHOS 生效。
4.4 Dart 通道协议分析
4.4.1 通道名称必须两端完全一致
const methodChannel = MethodChannel('flutter_web_auth_2');
通道名称属于跨语言协议。Dart 和 ArkTS 任何一端拼写不一致,都会出现“方法未实现”或“调用找不到插件”等问题。
4.4.2 发起认证并等待回调
平台接口的 MethodChannel 实现如下,位于仓库 flutter_web_auth_2_platform_interface/lib/method_channel/method_channel.dart:
import 'package:flutter/services.dart';
import 'package:flutter_web_auth_2_platform_interface/flutter_web_auth_2_platform_interface.dart';
/// Method channel implementation of the [FlutterWebAuth2Platform].
class FlutterWebAuth2MethodChannel extends FlutterWebAuth2Platform {
static const MethodChannel _channel = MethodChannel('flutter_web_auth_2');
Future<String> authenticate({
required String url,
required String callbackUrlScheme,
required Map<String, dynamic> options,
}) async =>
await _channel.invokeMethod<String>('authenticate', <String, dynamic>{
'url': url,
'callbackUrlScheme': callbackUrlScheme,
'options': options,
}) ??
'';
Future clearAllDanglingCalls() async =>
_channel.invokeMethod('clearAllDanglingCalls');
}
4.4.3 恢复前台时清理未完成请求
static Future<void> _clearAllDanglingCalls() async {
await _platform.clearAllDanglingCalls();
WidgetsBinding.instance.removeObserver(_resumedObserver);
}
authenticate 注册恢复前台观察器,回到前台会清理未完成调用并移除观察器。这个清理方法是库内私有方法,业务不能直接调用它。回调交付与 resumed 的实际顺序需在目标设备验证。
五、补全 OHOS 原生实现与工程配置
5.1 在 FlutterWebAuth2Plugin.ets 中实现原生能力
业务层沿用已有 API,原生侧在 FlutterWebAuth2Plugin 中接入 UIAbilityContext.openLink,通过 Flutter 通道回传结果。
下面列出类中的主要成员和方法,完整文件还包含 getUniqueClassName() 等插件接口;各段均为核心摘录,需要结合完整类使用。
原生插件位于:
ohos/src/main/ets/components/plugin/FlutterWebAuth2Plugin.ets
5.1.1 引入 Flutter 和 HarmonyOS 能力
import {
AbilityAware,
AbilityPluginBinding,
FlutterPlugin,
FlutterPluginBinding,
MethodCall,
MethodCallHandler,
MethodChannel,
MethodResult,
NewWantListener,
} from '@ohos/flutter_ohos';
import { AbilityConstant, Want } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
FlutterPlugin 负责接入 Flutter Engine 生命周期,MethodChannel 接收 Dart 命令;系统能力由 UIAbilityContext.openLink 提供。错误和事件处理以对应方法实现为准。
5.1.2 连接 Flutter Engine 和宿主 Ability
private channel: MethodChannel | null = null;
private abilityBinding: AbilityPluginBinding | null = null;
private readonly pendingAuthentications: Map<string, PendingAuthentication> =
new Map<string, PendingAuthentication>();
onAttachedToEngine(binding: FlutterPluginBinding): void {
this.channel = new MethodChannel(binding.getBinaryMessenger(), CHANNEL_NAME);
this.channel.setMethodCallHandler(this);
}
onAttachedToAbility(binding: AbilityPluginBinding): void {
this.abilityBinding = binding;
binding.addOnNewWantListener(this);
}
onDetachedFromAbility(): void {
this.abilityBinding?.removeOnNewWantListener(this);
this.abilityBinding = null;
this.cancelPendingAuthentications('CANCELED', 'The authentication session was canceled.');
}
Ability 连接时注册 NewWantListener,解绑时移除监听并取消 pending 请求。EntryAbility 需要由 FlutterAbility 正常转发 Want,不能绕开插件绑定。
5.1.3 打开认证页面并保存请求
private authenticate(call: MethodCall, result: MethodResult): void {
const authenticationUrl = String(call.argument('url') ?? '');
const callbackUrlScheme = String(call.argument('callbackUrlScheme') ?? '');
if (authenticationUrl.length === 0 || callbackUrlScheme.length === 0) {
result.error('INVALID_ARGUMENT', 'url and callbackUrlScheme are required.', null);
return;
}
const abilityBinding = this.abilityBinding;
if (abilityBinding === null) {
result.error('NO_ABILITY', 'The plugin is not attached to a UIAbility.', null);
return;
}
const normalizedScheme = callbackUrlScheme.toLowerCase();
if (this.pendingAuthentications.has(normalizedScheme)) {
result.error('AUTH_IN_PROGRESS', `Authentication is already active for scheme ${normalizedScheme}.`, null);
return;
}
const pending: PendingAuthentication = {
result: result,
};
this.pendingAuthentications.set(normalizedScheme, pending);
abilityBinding.getAbility().context.openLink(authenticationUrl, { appLinkingOnly: false })
.catch((error: BusinessError) => {
if (this.pendingAuthentications.get(normalizedScheme) !== pending) {
return;
}
this.pendingAuthentications.delete(normalizedScheme);
result.error('NO_BROWSER', 'No application can open the authentication URL.', {
code: error.code,
message: error.message,
});
});
}
先保存 pending,再发起 openLink;相同 Scheme 不允许并发认证。打开失败时仅清理仍属于本次调用的记录,避免误结束后续请求。
5.1.4 接收回调与结束待处理认证
onNewWant(want: Want, _launchParams: AbilityConstant.LaunchParam): void {
const callbackUrl = want.uri;
if (callbackUrl === undefined || callbackUrl.length === 0) {
return;
}
const callbackScheme = this.extractScheme(callbackUrl);
if (callbackScheme === null) {
return;
}
const pending = this.pendingAuthentications.get(callbackScheme);
if (pending === undefined) {
return;
}
this.pendingAuthentications.delete(callbackScheme);
pending.result.success(callbackUrl);
}
private cancelPendingAuthentications(code: string, message: string): void {
this.pendingAuthentications.forEach((pending: PendingAuthentication) => {
pending.result.error(code, message, null);
});
this.pendingAuthentications.clear();
}
回调按 Scheme 定位请求,先删除记录再返回完整 URI;没有 URI、没有 Scheme 或没有匹配请求时直接忽略。
5.1.5 处理 MethodChannel 命令
onMethodCall(call: MethodCall, result: MethodResult): void {
switch (call.method) {
case 'authenticate':
this.authenticate(call, result);
break;
case 'clearAllDanglingCalls':
this.cancelPendingAuthentications('CANCELED', 'User canceled login.');
result.success(null);
break;
default:
result.notImplemented();
break;
}
}
空参数返回 INVALID_ARGUMENT,无宿主返回 NO_ABILITY,同 Scheme 并发返回 AUTH_IN_PROGRESS,打开地址失败返回 NO_BROWSER。clearAllDanglingCalls 以 CANCELED 结束 pending;Engine 解绑使用 PLUGIN_DETACHED。
5.1.6 Engine 解绑时释放资源
onDetachedFromEngine(_binding: FlutterPluginBinding): void {
this.channel?.setMethodCallHandler(null);
this.channel = null;
this.cancelPendingAuthentications('PLUGIN_DETACHED', 'The plugin was detached from the Flutter engine.');
}
Engine 解绑取消仍未完成的认证并释放通道;Ability 解绑也会取消请求。插件不提供关闭外部浏览器页面的能力。
5.2 声明插件和宿主权限
示例宿主声明 INTERNET。回调接收的关键还包括 Ability 的 singleton 启动方式、browsable skill 和 Scheme 配置。
5.2.1 插件 HAR 的权限
插件 ohos/src/main/module.json5 的模块配置如下:
{
"module": {
"name": "flutter_web_auth_2",
"type": "har",
"deviceTypes": [
"default",
"tablet"
]
}
}
5.2.2 应用 entry 的权限
最终安装的是宿主应用。以下片段来自 example/ohos/entry/src/main/module.json5,合并时保留原有 Ability 等配置:
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
}
]
}
}
在现有 EntryAbility 配置中合并以下字段;保留原启动 skill,下面仅为回调 skill:
{
"name": "EntryAbility",
"exported": true,
"launchType": "singleton",
"skills": [
{
"entities": [
"entity.system.browsable"
],
"actions": [
"ohos.want.action.viewData"
],
"uris": [
{
"scheme": "foobar"
}
]
}
]
}
本文最小页面和仓库完整 Demo 使用 foobar;用于实际认证时,需同时替换 Dart、回调清单和服务端重定向地址中的 Scheme。HTTPS App Link 另需域名关联,不由这个 Scheme 配置自动完成。
5.3 注册并导出插件
pubspec.yaml 通过以下配置声明 OHOS 插件类:
flutter:
plugin:
platforms:
ohos:
pluginClass: FlutterWebAuth2Plugin
插件的 ohos/index.ets 需要导出实现:
import FlutterWebAuth2Plugin from './src/main/ets/components/plugin/FlutterWebAuth2Plugin';
export default FlutterWebAuth2Plugin;
执行 flutter pub get 和构建后,Flutter 工具会为应用生成插件注册代码。通常不应手工编辑 GeneratedPluginRegistrant.ets,因为下次构建可能覆盖它。
注册异常的排查步骤见第九节 MissingPluginException。
5.4 检查 example 的 OHOS 应用结构
本例的 example/ohos/build-profile.json5 应在 products 中设置版本。下面是需核对的配置片段,请合并到现有工程;其中 signingConfig: "default" 需要与本机配置的签名名称一致,签名材料保留在本地。
{
"app": {
"products": [
{
"name": "default",
"signingConfig": "default",
"compatibleSdkVersion": "5.1.0(18)",
"runtimeOS": "HarmonyOS",
"compileSdkVersion": "26.0.0",
"targetSdkVersion": "26.0.0"
}
]
},
"modules": [
{
"name": "entry",
"srcPath": "./entry",
"targets": [
{
"name": "default",
"applyToProducts": [
"default"
]
}
]
}
]
}
配置后,在 DevEco Studio 中执行一次 Sync Project。如果 entry 模块正常识别,Project 视图中会出现 entry,并能打开 entry 模块的签名配置。
六、补全交付文件并提交适配分支
6.1 除代码外还要补全哪些文件
代码适配完成后,还需要整理安装说明、接口文档、版本记录和开源信息。接收仓库有专用模板时,按其格式填写:
| 文件 | 应写清楚的内容 |
|---|---|
README.OpenSource | 上游名称、源码地址、适配版本或提交、版权及许可证信息;按仓库模板列出第三方依赖 |
README.md | 原项目说明、OHOS 支持入口、配套 Demo 和文档链接;保留上游信息 |
README.OpenHarmony_CN.md | 简介、AtomGit 安装方式、版本对应关系、环境约束、权限、接口表、示例、已验证范围和遗留问题 |
README.OpenHarmony.md | 与中文说明对应的英文文档 |
CHANGELOG.OpenHarmony.md | OHOS 新增能力、适配版本、兼容限制与测试范围 |
LICENSE / NOTICE | 保留上游许可证;NOTICE 按许可证和原项目要求保留或补充 |
example/README.md | 依赖方式、运行目录、签名、操作步骤与效果图;覆盖打开浏览器、成功回调、用户返回取消和同 Scheme 并发 |
pubspec.yaml、ohos/oh-package.json5 | 核对包名、版本、插件注册、仓库地址、许可证和依赖 |
.gitignore | 忽略构建缓存及本机签名材料,不漏提交必要源码和配置 |
README.OpenSource 记录库本身的来源与版本。本例的包名为 flutter_web_auth_2,版本为 6.0.0-alpha.7,采用 MIT 许可证;Flutter 和 HarmonyOS SDK 版本写入环境说明。
现有说明文件以目录树为准。README.OpenSource 等缺失交付文件按接收仓库要求补全;安装与反馈链接统一使用 AtomGit 地址。
6.2 提交前检查
提交前先完成第八节的插件、example 和真机测试,再从 Git 仓库根目录检查改动:
git branch --show-current
git diff --check
git status --short
git diff --stat
git diff
检查 diff 中的接口、平台注册和依赖变化,移除本机路径及签名信息,并使文档中的版本和分支与提交内容一致。
6.3 提交并推送到 AtomGit
文档和代码整理完成后,在 Git 仓库根目录暂存并提交。以下命令以第 6.1 节文档已经补全为前提,文件名按项目实际情况调整:
git add flutter_web_auth_2/lib flutter_web_auth_2/ohos flutter_web_auth_2/pubspec.yaml flutter_web_auth_2/example flutter_web_auth_2/test flutter_web_auth_2_platform_interface
git add flutter_web_auth_2/README.md flutter_web_auth_2/README.OpenSource flutter_web_auth_2/README.OpenHarmony_CN.md
git add flutter_web_auth_2/README.OpenHarmony.md flutter_web_auth_2/CHANGELOG.OpenHarmony.md
git diff --cached --check
git diff --cached --stat
git diff --cached
git commit -m "feat: add OHOS support for flutter_web_auth_2 6.0.0-alpha.7"
git remote -v
git branch --show-current
git push -u origin feat/ohos_flutter_web_auth_2_6.0.0-alpha.7
DevEco 可能向 example/ohos/build-profile.json5 写入本机签名配置,需要在提交前从暂存内容中移除。推送时,origin 应指向自己的 AtomGit 仓库,当前分支为 feat/ohos_flutter_web_auth_2_6.0.0-alpha.7。
推送后在 AtomGit 发起合并请求,说明上游来源和版本、OHOS 实现范围、依赖及权限、测试环境、操作结果、已知限制,并附 Demo 运行图。目标分支和评审流程以接收仓库要求为准。
七、使用插件包内 example 演示接入
插件包自带 example/,可以直接用来调试插件和体验浏览器认证回调。
7.1 本地适配时使用工作区依赖
该仓库使用 Pub workspace,Git 根目录 pubspec.yaml 的 workspace 包含主包、example 和 platform_interface;三者配置 resolution: workspace。示例中的依赖虽为版本约束,本地解析会选中工作区成员:
resolution: workspace
dependencies:
flutter:
sdk: flutter
flutter_web_auth_2: ^6.0.0-alpha.0
在 Git 仓库根目录执行 flutter pub get,再进入 flutter_web_auth_2/example 运行。这里由 workspace 解析本地包,无须改为 path: …/。
7.2 通过 AtomGit 引入插件
工作区外的业务应用通过 AtomGit 引入时,添加下面的 Git 依赖。仓库同步后,可固定到本文的本地参考提交:
dependencies:
flutter:
sdk: flutter
flutter_web_auth_2:
git:
url: https://atomgit.com/oh-flutter/flutter_web_auth_2.git
ref: 47153637dea216732cb7ae071224669d189a9b27
path: flutter_web_auth_2
使用自己的适配版本时,先推送分支,再将 url 改为对应仓库,ref 改为 feat/ohos_flutter_web_auth_2_6.0.0-alpha.7。正式发布后可固定到 tag 或 commit。
独立业务应用使用 Git 依赖时填写主包子目录 path。若需要与本地工作区完全一致的平台接口代码,将 flutter_web_auth_2_platform_interface 也指定到同一仓库、同一适配 ref,path 为 flutter_web_auth_2_platform_interface。仓库内 example 属于 workspace,验证独立 Git 接入应使用工作区外的业务应用,检查其根目录 pubspec.lock。
在工作区外的业务应用根目录执行:
flutter pub get
flutter pub deps
检查 该业务应用的 pubspec.lock 中 flutter_web_auth_2 的来源为 git,并核对 url、ref 和 resolved-ref。同时检查没有 dependency_overrides 或 pubspec_overrides.yaml 将其覆盖回本地依赖,确认应用使用的是 Git 依赖。
7.3 调用接口实现浏览器认证回调
下面的页面可用于插件包内的 example/lib/main.dart,是便于讲解的最小页面;仓库完整 Demo 的入口和布局可能不同,第八节截图与验收步骤以仓库完整 Demo 为准。
这是手机本机演示页面,只用于验证浏览器与 Scheme 往返,不执行真实账号认证。端口 43823 需要未被其他实例占用。
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_web_auth_2/flutter_web_auth_2.dart';
import 'dart:io';
void main() {
runApp(const MaterialApp(home: DemoPage()));
}
class DemoPage extends StatefulWidget {
const DemoPage({super.key});
State<DemoPage> createState() => _DemoPageState();
}
class _DemoPageState extends State<DemoPage> {
String _status = '尚未操作';
bool _busy = false;
late final Future<HttpServer> _serverReady;
void initState() {
super.initState();
_serverReady = HttpServer.bind(InternetAddress.loopbackIPv4, 43823);
_serverReady.then((server) {
if (!mounted) {
unawaited(server.close(force: true));
return;
}
server.listen((request) async {
request.response.headers.contentType = ContentType.html;
request.response.write(
'<a href="foobar://success?code=1337">Sign in</a>',
);
await request.response.close();
});
}, onError: (Object error) => _show('服务启动失败:$error'));
}
void _show(String value) {
if (mounted) setState(() => _status = value);
}
Future<void> _run(Future<String> Function() action) async {
if (_busy) return;
setState(() => _busy = true);
try {
_show(await action());
} catch (error) {
_show('调用失败:$error');
} finally {
if (mounted) setState(() => _busy = false);
}
}
void dispose() {
unawaited(_serverReady.then((server) async {
await server.close(force: true);
}, onError: (Object _) {}));
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('浏览器认证')),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
Text(_status),
const SizedBox(height: 16),
FilledButton(onPressed: _busy ? null : () => _run(() async {
final server = await _serverReady;
return await FlutterWebAuth2.authenticate(
url: 'http://127.0.0.1:${server.port}/',
callbackUrlScheme: 'foobar',
);
}), child: const Text('开始认证')),
],
),
);
}
}
7.4 页面退出时清理页面与认证相关资源
异步回调先检查 mounted,避免页面销毁后继续调用 setState。最小示例释放本地演示 HttpServer;库自身通过恢复前台观察器清理悬挂认证。页面 dispose 不是公开的取消认证 API,不应虚构 cancelAuthentication 方法。
八、验证、构建与鸿蒙设备运行效果
8.1 分别验证插件与 example
从插件仓库根目录执行:
flutter pub get
flutter analyze
flutter test
cd example
flutter pub get
flutter analyze
# 当前 example 没有 test/,补充页面测试后再执行 flutter test。
主包现有单元测试检查 authenticate 转发和非法 Scheme;本地 example 没有 test/ 目录。原生 NewWant 回调、取消时序和浏览器返回仍需真机验证。Widget 测试应匹配实际保留的 Demo 页面;如果替换成第七节最小页面,也要相应调整原来的 UI 断言。
Dart 测试覆盖接口和页面逻辑,打开浏览器、成功回调、用户返回取消和同 Scheme 并发还需要在鸿蒙设备上验证。
8.2 确认设备连接
hdc list targets
flutter devices
设备首次连接电脑时,需要在手机端确认调试授权。列表为空时,检查 USB 连接、调试模式和电脑授权。
8.3 配置签名
真机安装的 HAP 通常需要有效签名。推荐使用 DevEco Studio 为 entry 模块配置自动签名:
- 用 DevEco Studio 打开
example/ohos,不是仓库根目录; - 等待工程 Sync 成功,确认 Project 视图中存在
entry模块; - 打开 File > Project Structure > Signing Configs;
- 为
defaultproduct 选择或生成签名; - 确认设备、应用包名、证书和 Profile 匹配;
- 再回到终端执行 Flutter 构建或运行。
签名材料保存在本机,公开仓库中只保留构建所需的通用配置。
8.4 运行示例
以下命令在 example/ 目录执行,将 <device-id> 替换为设备列表中的实际 ID:
flutter run -d <device-id>
也可以先构建 HAP:
flutter build hap --debug
典型产物位于:
example/ohos/entry/build/default/outputs/default/
目录中通常包含已签名和未签名 HAP。真机安装应选择与当前设备匹配的已签名产物。
flutter build hap --release
hdc -t <device-id> install -r build/ohos/hap/entry-default-signed.hap
hdc -t <device-id> shell aa start -a EntryAbility -b com.linusu.flutter_web_auth_2_example
8.5 在设备上测试浏览器认证回调
- 运行完整 Demo,等待手机本机 127.0.0.1:43823 服务启动。
- 点击 Authenticate,确认浏览器显示本地登录演示页。
- 点击网页 Sign in,在浏览器的“此网站请求打开 App”提示出现后及时点击“打开”,确认 foobar://success?code=1337 回到应用。
- 检查 Status 中的 Got result 和完整回调地址。
- 再次打开认证页面,不点击 Sign in 而直接返回,观察 CANCELED。
- 测试相同 Scheme 并发、错误 Scheme 和无法处理的认证地址,核对错误码。
页面初始提示来自 Demo 默认值,调用结果或事件到达后才反映系统状态。
8.6 鸿蒙设备运行效果
OHOS 实现提供浏览器认证回调。以下为仓库完整 Demo 的三张真机运行截图,按实际状态记录。
| 认证入口 | 浏览器页面 | 回调结果 |
|---|---|---|
| 认证前页面 | 本地演示登录页 | foobar 回调已返回 |
自定义 Scheme 必须与宿主 Ability 清单一致,并有浏览器或应用能够处理认证地址。OHOS 当前按 Scheme 匹配回调,不替业务验证 OAuth state 或认证结果。
九、FAQ:适配过程与使用问题
9.1 Missing SDK components
典型错误如下:
Missing SDK components. SDK path: ...,
missing components: toolchains,ets,js,native,previewer.
这个错误发生在 Hvigor 同步阶段。通常需要检查构建工具使用的 SDK 路径、组件是否完整,以及 Hvigor 与 SDK 的版本是否匹配。
处理顺序:
- 在 DevEco Studio SDK Manager 中确认 API 26 组件已经下载完整;
- 检查 Flutter 和 DevEco Studio 使用的 SDK 路径是否一致;
- 避免误用
/Applications/DevEco-Studio.app/Contents/sdk之类的不完整目录; - 确认 SDK 根目录下存在
toolchains、ets、js、native、previewer; - 执行
flutter config --ohos-sdk <正确路径>; - 重新执行
flutter doctor -v和 DevEco Studio Sync。
因为 Sync 和 Compile 首先读取 Mac 本地 SDK。手机 API 版本只在部署、安装和运行时参与兼容判断。即使完全不连接手机,本地 SDK 不完整时也会得到相同错误。
当前工程的 compatibleSdkVersion 是 API 18,因此 API 24 在安装版本门槛上是满足的;但功能是否可用还取决于目标系统能力、权限和运行环境,不能仅凭最低版本判断。
9.2 DevEco Studio 中看不到 entry 模块
插件的 ohos/ 目录是 HAR 模块,可安装应用的 entry 模块位于 example/ohos/entry。
请直接使用 DevEco Studio 打开:
flutter_web_auth_2/flutter_web_auth_2/example/ohos
如果仍看不到 entry,先解决 SDK Sync 错误,再检查 example/ohos/build-profile.json5 的 modules 是否包含 ./entry。同步失败时,Project Structure 无法正确解析模块,签名界面也可能不显示 entry。
9.3 无法手动签名
签名配置依附于可构建的应用模块和 product。只有 HAR 插件模块、工程 Sync 失败,或者打开了错误目录时,DevEco Studio 都可能无法提供 entry 签名入口。
建议先确认:
- 打开的是
example/ohos; - SDK 组件完整并且 Sync 成功;
entry的模块类型为entry;defaultproduct 和 target 已正确关联;- 当前账号、证书和调试设备状态有效。
9.4 认证后无法返回应用
核对服务端回调、callbackUrlScheme 与 EntryAbility skill 的 scheme;示例均为 foobar。确认 Ability 为 singleton,onNewWant 经 FlutterAbility 转发。浏览器能打开网页不能证明回调注册正确。
9.5 MissingPluginException
这通常表示 Dart 通道找不到已注册的原生插件。新增原生插件后需要重新构建应用。从仓库根目录执行:
cd example
flutter clean
flutter pub get
flutter run -d <device-id>
如果仍然出现,检查自动生成的插件注册文件中是否包含 FlutterWebAuth2Plugin,同时核对 pubspec.yaml、ohos/index.ets 和 oh-package.json5。
9.6 重复点击提示 AUTH_IN_PROGRESS
pendingAuthentications 按小写 Scheme 保存请求,同 Scheme 第二次调用会失败。页面用 busy 状态限制重复提交,完成或取消后再发起下一次;不同 Scheme 的行为不能套用同一请求。
9.7 编译成功但安装失败
常见原因包括:
- HAP 未签名或使用了错误的 Profile;
- 设备未加入调试设备列表;
- 包名与签名 Profile 不匹配;
- 安装包的
compatibleSdkVersion高于设备 API; - 手机上已经安装了使用不同证书签名的同包名应用。
根据安装错误码区分签名、版本和包名冲突,再处理对应配置。
9.8 flutter create 不认识 ohos,或包名不合法
先执行 flutter --version,确认使用的是 OHOS 版工具链,环境配置回到第二节的社区链接核对。包名报错时,确认当前目录包含目标插件的 pubspec.yaml,并显式传入 --project-name flutter_web_auth_2;本例 Dart 包名为 flutter_web_auth_2,多包仓库需先进入对应插件包目录。生成后检查 diff,再补充 ArkTS 业务实现。
9.9 AtomGit 依赖提示找不到分支或无权限
先检查 URL 是否指向已同步的目标仓库,再确认 feat/ohos_flutter_web_auth_2_6.0.0-alpha.7 已推送。仓库未创建、适配分支未推送或提交未同步时,应先完成同步;不能直接使用仅存在本地的提交号。私有仓库还需在本机配置 Git 认证。
9.10 改了本地 ArkTS,Demo 为什么没变化
先区分仓库内的 Pub workspace 和工作区外的业务应用:仓库内示例从 workspace 解析本地包,检查 Git 根目录的 pubspec.lock;工作区外的 Git 依赖读取远程提交,本地联调可改为正确的本地包路径,测试远程版本则同步代码并核对业务应用 pubspec.lock 的 resolved-ref。原生代码变动后停止应用并重新构建运行,不能只做 Dart 热重载。
9.11 设置 timeout 或无痕选项后为什么没有变化
当前 OHOS 原生实现不读取 options Map。示例中的 timeout: 5 不代表鸿蒙端五秒后自动超时。无痕、浏览器优先级等其他平台配置同样未实现;回到前台清理与超时是不同机制。
相关链接
更多推荐




所有评论(0)