杰理 SDK 文档中心
首页
首页
  • 概述与快速开始

    • 项目简介与功能总览
    • 运行环境与快速开始
    • 工程结构与文档布局
  • 架构与核心机制

    • 插件架构与原生平台桥接
    • 基类管理器与常量体系
    • 事件流与接收通知机制
  • 蓝牙连接与设备管理

    • 蓝牙连接与状态管理
    • 设备信息、配置与按键设置
    • 双设备连接与多链路管理
    • 数据传输与自定义命令
  • 音乐与媒体控制

    • 设备音乐与手机音乐播放控制
    • 音量与音频输出管理
  • 音效与音频模式

    • 均衡器与音效调节
    • 音频模式与降噪(ANC)设置
    • Auracast 音频广播
  • 设备功能控制

    • 闹钟管理
    • FM 收音机控制
    • 灯光控制
    • 充电仓与彩屏仓管理
  • 示例应用:杰理之家 Demo

    • 应用框架与交互组件
    • 设置、多语言与调试
  • 接口参考与文档中心

    • 发送接口参考
    • 接收接口与事件参考
    • 官方文档与集成指南

应用框架与交互组件

本文档介绍 JieLi_Home Demo 应用(code/JieLi_Home_Demo/example)中基于 Flutter 的应用框架与交互组件体系:data/ 目录下的管理器(Manager)如何组织业务能力、dialog/ 目录下的弹窗组件如何实现通用交互,以及两者之间如何通过 showDialog / Navigator 与 jl_home 插件包协同工作。

Purpose and Scope

本页面覆盖以下内容:

  • 应用框架层:以 DialogManager 为代表的「管理器(Manager)」模式——每个业务域一个 Manager,负责弹窗的构建(build)与展示(show),并通过 MethodChannel 与原生层通信。
  • 交互组件层:dialog/ 目录下的通用弹窗组件体系,重点深入分析通用确认弹窗 CustomConfirmDialog(标题、消息、单/双按钮、国际化、样式常量)。
  • 框架协作机制:showDialog → Navigator.push → 用户操作 → Navigator.pop → 回调(VoidCallback)的完整控制流,以及 context.mounted 异步安全模式。

以下主题属于兄弟页面,本文仅作交叉引用、不深入展开:

  • OTA 升级的具体业务流程(OtaDialog、ota_file_manager 的内部状态机)→ 见 OTA 升级相关页面。
  • 语言切换与翻译管理(select_language_manager、translate_page_manager)→ 见国际化页面。
  • 设备配对/连接链路(face_to_face_manager、MTU 调整等)→ 见设备连接页面。
  • 设置项持久化(setting_manager)→ 见设置页面。

Overview

这是一个 Flutter 插件示例工程(example),宿主业务全部围绕蓝牙设备(杰理科技 JL 系列)的发现、连接、OTA 升级与通话翻译展开。由于业务天然是「任务 + 确认 + 结果反馈」的交互模型,弹窗(Dialog)成为最主要的交互载体,由此沉淀出一套统一的应用框架约定:

  1. 管理器与 UI 分离:业务逻辑收敛在 data/*_manager.dart,弹窗组件收敛在 dialog/*.dart。页面只负责调用 Manager,Manager 负责组装弹窗并决定何时展示。
  2. 构建与展示分离:Manager 提供 buildXxxDialog()(返回 Widget)与 showXxxDialog()(内部调用 showDialog 并 await 结果),便于单元测试与复用。
  3. 回调而非返回值:弹窗组件通过 VoidCallback?(onConfirm / onCancel)通知业务层用户的选择,而不是自己处理业务,从而保持组件无业务依赖、可任意复用。
  4. 统一视觉规范:弹窗圆角 12、确认主色 0xFF398BFF、取消色 0xFFB0B0B0、正文色 0xFF242424、分割线 0xFFF5F5F5,按钮高度取自插件包常量 AppConstants.dialogButtonHeight。
  5. 国际化内置:所有文案默认值来自 AppLocalizations(l10n/app_localizations.dart),组件本身不硬编码字符串。
  6. 原生桥接:需要读写文件等原生能力的弹窗(如 SelectFileSaveDialog)通过 MethodChannel 与宿主原生侧通信。

Architecture

下图展示了「页面 → 管理器 → 弹窗组件 → 插件包/框架」的分层关系(节点均取自真实源码):

flowchart TD
    subgraph sg_App["Demo 应用 (example/lib)"]
        subgraph sg_Manager["管理器层 (data/)"]
            DialogManager["DialogManager"]
        end

        subgraph sg_Dialog["交互组件层 (dialog/)"]
            CustomConfirmDialog["CustomConfirmDialog"]
            OtaDialog["OtaDialog"]
            SelectFileSaveDialog["SelectFileSaveDialog"]
        end
    end

    subgraph sg_Plugin["jl_home 插件包"]
        AppConstants["AppConstants (constants.dart)"]
        AppLocalizations["AppLocalizations (l10n)"]
    end

    subgraph sg_Framework["Flutter Framework"]
        ShowDialog["showDialog()"]
        NavigatorPop["Navigator.pop()"]
    end

    DialogManager -->|"buildOtaDialog() 构建"| OtaDialog
    DialogManager -->|"buildSaveFileDialog() 构建"| SelectFileSaveDialog
    DialogManager -->|"注入 MethodChannel"| SelectFileSaveDialog
    DialogManager -->|"await showDialog()"| ShowDialog
    CustomConfirmDialog --> AppConstants
    CustomConfirmDialog --> AppLocalizations
    SelectFileSaveDialog --> AppLocalizations
    OtaDialog --> AppLocalizations
    CustomConfirmDialog --> NavigatorPop
    OtaDialog --> NavigatorPop

分层职责说明:

  • 管理器层(data/):DialogManager 持有页面 BuildContext 与 MethodChannel 两个依赖(见 dialog_manager.dart#L8-L12)。它的职责是"知道什么时候弹什么窗",而不是"弹窗长什么样"。
  • 交互组件层(dialog/):纯展示组件,只接收数据与回调。CustomConfirmDialog 不依赖任何 Manager,仅依赖插件包常量与本地化资源,这是它能在全应用复用的根本原因。
  • 插件包(jl_home):AppConstants.dialogButtonHeight 提供统一尺寸常量;AppLocalizations 提供多语言文案。两者都是静态/单例式访问,组件通过 import 'package:jl_home/...' 与相对路径 ../l10n/app_localizations.dart 引入。
  • Flutter 框架:showDialog 负责把弹窗 Widget 推入路由栈,Navigator.pop 负责关闭;showDialog 返回的 Future 与 context.mounted 检查共同构成异步安全闭环。

设计意图:分层的关键在于依赖方向单向——页面依赖 Manager,Manager 依赖弹窗组件,组件只依赖框架与常量。这样新增一个弹窗只需"加一个组件 + 在 Manager 加一个方法",不触碰其他模块;而统一文案与样式常量则保证全应用弹窗视觉与语言的一致性。

主内容:管理器模式(应用框架核心)

DialogManager:构建与展示分离

DialogManager 是应用框架的样板实现,完整源码仅 31 行,却定义了整个 Demo 的弹窗组织范式(dialog_manager.dart):

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

import '../dialog/ota_dialog.dart';
import '../dialog/select_file_save_dialog.dart';

/// Manages the display of various dialogs related to OTA operations.
class DialogManager {
  final BuildContext context;
  final MethodChannel methodChannel;

  DialogManager({required this.context, required this.methodChannel});

  Widget buildSaveFileDialog(String fileName) {
    return SelectFileSaveDialog(fileName: fileName, methodChannel: methodChannel);
  }

  Widget buildOtaDialog() {
    return OtaDialog();
  }

  Future<void> showOtaDialog() async {
    final dialog = buildOtaDialog();
    await showDialog(context: context, builder: (context) => dialog);
  }

  Future<void> showSaveFileDialog(String fileName) async {
    final dialog = buildSaveFileDialog(fileName);
    await showDialog(context: context, builder: (context) => dialog);
  }
}

Source: dialog_manager.dart

逐段剖析其设计意图:

  1. 构造注入依赖(L9-L12):context 与 methodChannel 通过构造函数注入而非全局单例。这使得 Manager 可以被测试替身替换,也避免了 context 跨页面泄漏——每个页面实例化自己的 Manager 时传入自己作用域内的 context。
  2. buildXxxDialog() 返回 Widget(L14-L20):构建方法不执行展示,只负责组装组件。buildSaveFileDialog 展示了依赖传递的典型写法——把 Manager 持有的 methodChannel 注入到需要原生能力的弹窗中。
  3. showXxxDialog() 负责展示(L22-L30):展示方法内部调用 Flutter 的 showDialog,并把 builder 直接返回已构建好的组件实例。await 关键字说明调用方可等待弹窗关闭后继续执行——这是"弹窗即 Future"的异步交互模型。
  4. 领域聚合(L4-L5、L18-L19):Manager 的命名与注释(OTA operations)表明它聚合了一个业务域内的全部弹窗,而不是每个弹窗一个 Manager。后续新增 OTA 相关弹窗(如 MTU 调整、进度展示)都应挂载到这个 Manager 上。

Manager 模式家族

data/ 目录下与 DialogManager 并列的其他管理器(本页未逐一读取其实现,职责依据文件命名与目录结构推断,见各文件):

文件推断职责相关弹窗/页面
dialog_manager.dartOTA 弹窗聚合管理(已读取)OtaDialog、SelectFileSaveDialog
ota_file_manager.dartOTA 固件文件的选择与校验select_file_save_dialog.dart
face_to_face_manager.dart面对面(点对点)连接流程device_password_dialog.dart 等
popup_menu_manager.dart页面 PopupMenu 菜单项的组织页面菜单
select_language_manager.dart语言选择逻辑select_language 相关页面
setting_manager.dart设置项读写与状态设置页面
translate_page_manager.dart通话翻译页业务call_translation_tips_dialog.dart

说明:上述推断基于文件命名与 Demo 业务常识;除 DialogManager 外其余 Manager 的内部实现细节未在本页读取,请以对应源码为准。

主内容:通用确认弹窗 CustomConfirmDialog

CustomConfirmDialog 是交互组件层的基石组件——一个零业务依赖、可配置标题/消息/按钮的通用确认弹窗(custom_confirm_dialog.dart)。

可配置接口

class CustomConfirmDialog extends StatelessWidget {
  final VoidCallback? onCancel;
  final VoidCallback? onConfirm;
  final String? title;
  final String? message;
  final String? cancelText;
  final String? confirmText;
  final bool showTitle;
  final bool showSingleButton;

  static const Color darkTextColor = Color(0xFF242424);
  static const Color confirmTextColor = Color(0xFF398BFF);
  static const Color cancelTextColor = Color(0xFFB0B0B0);
  static const Color dialogDividerColor = Color(0xFFF5F5F5);

  const CustomConfirmDialog({
    super.key,
    this.onCancel,
    this.onConfirm,
    this.title,
    this.message,
    this.cancelText,
    this.confirmText,
    this.showTitle = true,
    this.showSingleButton = false,
  });
  ...
}

Source: custom_confirm_dialog.dart

设计要点:

  • 全部参数可空 + 默认值兜底:title/message/按钮文案均可省略。组件在 build 中通过 AppLocalizations 计算有效值(effectiveMessage = message ?? loc.saveAndRestartMessage,effectiveConfirmText = confirmText ?? loc.restart),因此调用方只需传 onConfirm 一个参数即可得到一个"重启确认框"。兜底文案来自本地化资源而非硬编码,这是国际化的正确姿势。
  • StatelessWidget + const 构造:组件无内部状态,所有展示信息由构造参数决定,可安全地 const 实例化,避免无谓重建。
  • 双开关控制形态:showTitle 控制是否显示标题;showSingleButton 控制是单按钮(仅确认)还是双按钮(取消 + 确认)形态。

视觉与布局实现

return Dialog(
  shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
  child: Material(
    borderRadius: BorderRadius.circular(12),
    color: Colors.white,
    child: Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        if (showTitle && effectiveTitle.isNotEmpty) _buildTitle(effectiveTitle),
        _buildMessage(effectiveMessage),
        const Divider(height: 1, color: dialogDividerColor),
        _buildButtons(
          context,
          effectiveCancelText,
          effectiveConfirmText,
          cancelTextColor,
          confirmTextColor,
        ),
      ],
    ),
  ),
);

Source: custom_confirm_dialog.dart

结构自上而下为:可选标题 → 消息 → 1px 分割线 → 按钮区。关键实现细节:

  • 外层 Dialog 提供系统级弹窗语义(遮罩、圆角裁剪、居中布局),Material 保证水波纹等 Material 组件可用,Column(mainAxisSize: MainAxisSize.min) 让弹窗高度收缩到内容。
  • 标题字号 18、加粗;消息字号 15、常规;两者统一使用 darkTextColor(0xFF242424),居中显示。
  • 消息顶部间距是动态的(showTitle && title 非空 ? 12 : 32):有标题时消息贴近标题,无标题时消息上移补偿,保证视觉重心不偏。
  • 分割线用 Divider(height: 1) 压成 1px 细线,颜色 0xFFF5F5F5 与按钮区分隔。

按钮区:单/双按钮与安全回调

Widget _buildButtons(BuildContext context, String cancelText, String confirmText,
    Color cancelColor, Color confirmColor) {
  if (showSingleButton) {
    return _buildSingleButton(context, confirmText, confirmColor);
  }
  return _buildDoubleButtons(context, cancelText, confirmText, cancelColor, confirmColor);
}

Source: custom_confirm_dialog.dart

双按钮模式采用 Row + 两个 Expanded 各占一半宽度、中间夹 1px 竖分割线的布局(L121-L179)。每个按钮都是 InkWell(关闭水波纹/高亮、透明 splashColor/highlightColor),高度统一为 AppConstants.dialogButtonHeight。取消按钮文字色 0xFFB0B0B0 常规字重,确认按钮文字色 0xFF398BFF 加粗——用字重与颜色双重编码主次操作,符合移动端确认框惯例。

点击处理是异步安全的样板(以确认按钮为例):

Expanded(
  child: InkWell(
    splashColor: Colors.transparent,
    highlightColor: Colors.transparent,
    onTap: () {
      if (context.mounted) {
        Navigator.pop(context);
        onConfirm?.call();
      }
    },
    ...
  ),
),

Source: custom_confirm_dialog.dart

context.mounted 检查是 Flutter 3.7+ 的标准防御:在异步回调或延迟场景中,Widget 可能已从树中移除,此时调用 Navigator.pop(context) 会抛异常。先关窗(Navigator.pop)再回调(onConfirm?.call())的顺序保证:回调执行时弹窗必然已关闭,业务层可以直接继续下一步,不会被仍在栈顶的弹窗遮挡。

交互组件族谱

dialog/ 目录共 13 个弹窗组件,均遵循上述"无业务、可配置、回调通知"的范式。除 CustomConfirmDialog 外,其余组件的内部实现本页未逐一读取,用途依据文件命名与 DialogManager 中的引用关系整理:

组件文件用途(依据命名/引用推断)关联管理器
custom_confirm_dialog.dart通用确认弹窗(已读取)各业务页面
loading_dialog.dart通用加载中转圈各业务页面
loading_content_dialog.dart带内容/进度描述的加载弹窗OTA 等长任务
custom_loading_dialog.dart定制样式加载弹窗OTA 等长任务
ota_dialog.dartOTA 升级主弹窗(已确认被 DialogManager 引用)DialogManager
select_file_save_dialog.dart选择固件保存路径(已确认接收 MethodChannel)DialogManager、OtaFileManager
mtu_adjustment_dialog.dartMTU 参数调整设备连接
device_filter_dialog.dart设备过滤条件选择设备列表
device_password_dialog.dart设备密码输入FaceToFaceManager
multi_links_dialog.dart多设备同时连接管理设备连接
rename_dialog.dart设备重命名设备详情
call_translation_tips_dialog.dart通话翻译提示TranslatePageManager

诚实性说明:表中除已标注"已读取/已确认"的条目外,均为依据文件命名与 Demo 业务域的合理推断;若需精确行为请直接阅读对应源文件。

核心流程:弹窗展示与回调闭环

展示时序(Manager 驱动)

一次典型的弹窗交互(以 showOtaDialog 为例)在组件间的时间顺序如下:

sequenceDiagram
    participant Page as 业务页面
    participant DM as DialogManager
    participant FD as showDialog (Flutter)
    participant Dlg as OtaDialog / 弹窗组件
    participant Nav as Navigator

    Page->>DM: showOtaDialog()
    DM->>DM: buildOtaDialog() 构建组件实例
    DM->>FD: showDialog(context: context, builder: (c) => dialog)
    FD->>Nav: push 路由 (弹窗入栈)
    Nav-->>Page: 返回 Future (await 挂起)
    Page-->>Dlg: 弹窗渲染,等待用户操作
    Dlg->>Dlg: 用户点击确认/取消按钮
    Dlg->>Nav: Navigator.pop(context)
    Nav-->>FD: 路由出栈
    FD-->>Page: Future 完成,await 恢复
    Page->>Page: 继续后续业务逻辑

关键点:await 让页面在弹窗展示期间挂起,弹窗关闭后从 showDialog 处继续执行。因此弹窗交互本质上是"Future 化"的——业务代码可以像写同步逻辑一样组织"先确认、再执行"的流程,无需监听器或事件总线。

弹窗内部决策流(CustomConfirmDialog)

flowchart TD
    Start(["build(context)"]) --> L10n["获取 AppLocalizations 兜底文案"]
    L10n --> TitleCheck{"showTitle 且标题非空?"}
    TitleCheck -->|"是"| BuildTitle["渲染标题 (18px 加粗)"]
    TitleCheck -->|"否"| SkipTitle["跳过标题"]
    BuildTitle --> Message["渲染消息 (15px)"]
    SkipTitle --> Message
    Message --> Divider["渲染 1px 分割线"]
    Divider --> ModeCheck{"showSingleButton?"}
    ModeCheck -->|"是"| Single["单按钮 (仅确认)"]
    ModeCheck -->|"否"| Double["双按钮 (取消+确认)"]
    Single --> Tap{"用户点击按钮"}
    Double --> Tap
    Tap -->|"点击"| Mounted{"context.mounted?"}
    Mounted -->|"否"| Skip["跳过回调 (防崩溃)"]
    Mounted -->|"是"| Pop["Navigator.pop(context)"]
    Pop --> Callback["调用 onConfirm / onCancel 回调"]
    Callback --> End(["结束"])
    Skip --> End

决策顺序的设计逻辑:组件先做文案兜底(保证任何参数组合都能渲染出合理内容),再按开关决定布局形态,最后在交互出口统一做 context.mounted 防御。所有分支都收敛到"弹窗关闭 + 回调通知"两个动作,业务侧无需关心组件内部形态。

使用示例

示例一:通过 DialogManager 展示 OTA 弹窗

页面持有 Manager 实例,直接调用 showXxxDialog 即可;展示与关闭的细节全部封装在 Manager 内:

final DialogManager _dialogManager = DialogManager(
  context: context,
  methodChannel: methodChannel,
);

// 用户点击"OTA 升级"后
await _dialogManager.showOtaDialog();

// 用户点击"选择保存文件"后
await _dialogManager.showSaveFileDialog('firmware_v1.2.3.bin');

Source: dialog_manager.dart

示例二:直接使用 CustomConfirmDialog(默认双按钮确认)

业务页面里最常见的用法——只传必要参数,其余走本地化默认值(默认场景为"重启确认"):

await showDialog(
  context: context,
  builder: (context) => CustomConfirmDialog(
    onConfirm: () => _restartApp(),
  ),
);

Source: custom_confirm_dialog.dart

示例三:完全自定义的确认框(标题 + 消息 + 单按钮)

当需要"仅提示、仅一个确认动作"的场景(如操作成功提示),关闭标题并切换单按钮模式:

showDialog(
  context: context,
  builder: (context) => CustomConfirmDialog(
    showTitle: false,
    showSingleButton: true,
    message: loc.operationSucceeded,
    confirmText: loc.gotIt,
    onConfirm: () => Navigator.of(context).pop(),
  ),
);

Source: custom_confirm_dialog.dart

说明:示例三中的 loc.operationSucceeded / loc.gotIt 为示意性本地化键(AppLocalizations 实际键名以 app_localizations.dart 为准),重点在于演示 showTitle/showSingleButton/confirmText 三个开关的组合用法。

配置选项

CustomConfirmDialog 构造参数

参数类型默认值说明
onCancelVoidCallback?null点击取消按钮(或单按钮模式外)时回调;为 null 时仅关闭弹窗
onConfirmVoidCallback?null点击确认按钮时回调;为 null 时仅关闭弹窗
titleString?null(空字符串)弹窗标题;配合 showTitle 使用
messageString?loc.saveAndRestartMessage弹窗正文;缺省时使用本地化的"保存并重启"文案
cancelTextString?loc.cancel取消按钮文案;缺省时使用本地化"取消"
confirmTextString?loc.restart确认按钮文案;缺省时使用本地化"重启"
showTitlebooltrue是否渲染标题区;为 false 时消息顶部 padding 增大以补偿视觉重心
showSingleButtonboolfalsetrue 时只渲染单个确认按钮;false 时渲染"取消 + 确认"双按钮

视觉常量(组件内定义,不可外部覆盖)

常量值用途
darkTextColorColor(0xFF242424)标题与消息文字颜色
confirmTextColorColor(0xFF398BFF)确认按钮文字颜色(主操作)
cancelTextColorColor(0xFFB0B0B0)取消按钮文字颜色(次操作)
dialogDividerColorColor(0xFFF5F5F5)消息/按钮间分割线颜色
AppConstants.dialogButtonHeight插件包常量按钮区高度(来自 jl_home 插件包 constants.dart)

DialogManager 构造依赖

参数类型说明
contextBuildContext调用方页面上下文,用于 showDialog
methodChannelMethodChannel原生通道,透传给需要原生能力的弹窗(如 SelectFileSaveDialog)

API 参考

DialogManager

DialogManager({required this.context, required this.methodChannel})

说明:OTA 相关弹窗的管理器,采用构造注入依赖、构建与展示分离的设计。

Widget buildSaveFileDialog(String fileName)

  • 参数:fileName(String)——待保存的固件文件名。
  • 返回:SelectFileSaveDialog 组件实例(内部注入 methodChannel)。
  • 抛错:无(纯构建)。

Widget buildOtaDialog()

  • 参数:无。
  • 返回:OtaDialog 组件实例。
  • 抛错:无(纯构建)。

Future<void> showOtaDialog()

  • 参数:无。
  • 返回:Future<void>,弹窗关闭时完成。
  • 行为:构建 OtaDialog 并 await showDialog(context: context, ...) 展示。

Future<void> showSaveFileDialog(String fileName)

  • 参数:fileName(String)——待保存的固件文件名。
  • 返回:Future<void>,弹窗关闭时完成。
  • 行为:构建 SelectFileSaveDialog 并展示。

CustomConfirmDialog

const CustomConfirmDialog({
  super.key,
  this.onCancel,
  this.onConfirm,
  this.title,
  this.message,
  this.cancelText,
  this.confirmText,
  this.showTitle = true,
  this.showSingleButton = false,
})

说明:通用确认弹窗(StatelessWidget),通过回调而非返回值与业务层通信。

参数:见上文"配置选项"表。

返回/抛错:无返回值、不抛异常;回调可空、context.mounted 防御保证点击处理不会因 Widget 已销毁而崩溃。

失败模式、边界情况与并发

1. 异步回调中的 context 失效

弹窗按钮回调可能发生在 Widget 已从树中移除之后(例如页面在弹窗展示期间被路由替换)。若此时直接 Navigator.pop(context) 会抛出 FlutterError。组件统一用 if (context.mounted) 包裹(见 custom_confirm_dialog.dart#L158-L163):已卸载则静默跳过,避免崩溃。

2. 可空回调与文案兜底

所有回调与文案参数均可为空。回调为空时弹窗"只关窗不做事";文案为空时使用 AppLocalizations 本地化默认值。代价是语义耦合:默认文案围绕"保存并重启"场景设计,若业务需其他语义必须显式传 message/confirmText,否则会出现与预期不符的文案——这是该组件当前已知的边界约束。

3. 弹窗并发与路由栈

Flutter 的 showDialog 基于 Navigator.push,多个弹窗连续弹出会依次入栈,后弹出的盖住先弹出的。由于所有 showXxxDialog 都返回 Future,页面可用 await 串行化弹窗流程;若并发调用(未 await)则表现为栈式叠加,关闭顺序为后进先出。Manager 层不提供互斥队列,需要串行语义时由页面自行 await——这是框架的有意取舍(简单、可预测)。

4. 单按钮模式的取消语义

showSingleButton: true 时组件不渲染取消按钮,但用户仍可点击遮罩或系统返回键关闭弹窗(Dialog 默认 barrierDismissible 行为未显式关闭),此时 onCancel 不会被调用(单按钮分支只接 onConfirm)。需要拦截遮罩关闭时,应在调用处改用 showDialog(barrierDismissible: false, ...) 包裹。

5. MethodChannel 的线程与平台边界

SelectFileSaveDialog 通过注入的 MethodChannel 发起原生调用。Demo 中该通道由宿主原生侧实现;若原生侧未注册对应 handler,调用会抛出 MissingPluginException。Manager 层不捕获此异常,由业务页面在 await showXxxDialog() 之后统一处理(或依赖全局错误上报)。

性能与运维注意事项

  • 组件均为无状态轻量 Widget:CustomConfirmDialog 是 StatelessWidget 且支持 const 构造,重建成本极低;弹窗内容为单个 Column + 少量 Text/InkWell,无图片、无动画,对低端设备友好。
  • 无网络/无轮询:弹窗体系不引入额外网络请求;长任务进度类弹窗(loading_content_dialog、ota_dialog)由业务层驱动刷新,组件本身不持有定时器,避免后台泄漏。
  • showDialog 的挂起成本:await showDialog(...) 期间页面 BuildContext 保持活跃。若弹窗长时间不关闭(如等待用户选择文件),页面处于"逻辑挂起"状态——这是设计预期,但应避免在弹窗展示期间发起与结果无关的重型计算。
  • 导航栈深度:连续弹出多个弹窗会叠加路由栈深度;Demo 场景(确认 → 选择文件 → 进度)最多约 3 层,Flutter 无实际问题。若未来出现更深嵌套,建议改用全屏页面而非弹窗。

扩展点

  1. 新增业务弹窗的标准流程:

    • 在 dialog/ 下新建组件(继承 StatelessWidget,参数可空 + 本地化兜底 + VoidCallback 回调);
    • 在对应 Manager(如 DialogManager)中增加 buildXxxDialog() 与 showXxxDialog() 两个方法;
    • 页面通过 Manager 展示,业务逻辑放在回调中。 按此流程新增的弹窗会自动获得:统一视觉、国际化、异步安全与可测试性。
  2. 复用 CustomConfirmDialog 的形态开关:showTitle/showSingleButton/文案参数组合可覆盖"确认、提示、成功反馈"三类常见场景;若需要第三按钮或自定义内容区,应新建组件而不是给 CustomConfirmDialog 堆参数(保持其单一职责)。

  3. 替换视觉常量:dialogButtonHeight 来自插件包 AppConstants,是全局统一尺寸入口;调整一处即可全局生效。组件内的四个 static const Color 为局部常量,如需要主题化改造,可将其提升为构造参数或主题扩展。

  4. Manager 依赖注入:DialogManager 通过构造注入 context 与 methodChannel,为单元测试提供了替身(mock)接口——测试时可用测试 BuildContext 与 mock 通道验证弹窗构建逻辑。

测试

仓库根级集成测试入口为 plugin_integration_test.dart(integration_test 目录),本页未读取其断言细节。基于源码结构可确认的测试关注点:

  • 组件级:CustomConfirmDialog 纯函数式构造(参数 → 布局形态),易于用 widget test 断言标题/消息/按钮数量与回调触发。
  • 管理器级:DialogManager 的 build/show 分离设计使测试可以仅验证 buildXxxDialog 返回的组件类型与参数透传,而无需真实展示弹窗。
  • 集成级:Demo 以蓝牙设备交互为主,端到端弹窗流程依赖真实设备或 mock 通道,适合 integration_test 覆盖。

测试细节未在本页展开,具体断言请阅读对应测试文件。

相关链接

  • 源码入口:
    • dialog_manager.dart — 管理器模式样板
    • custom_confirm_dialog.dart — 通用确认弹窗
    • app_localizations.dart — 本地化资源
    • constants.dart — 插件包常量(AppConstants)
  • 相关目录:
    • example/lib/dialog/ — 全部 13 个弹窗组件
    • example/lib/data/ — 全部 7 个业务管理器
  • 交叉引用:OTA 升级流程、设备连接与 MTU 调整、语言与翻译管理、设置持久化等主题请参见对应 Wiki 页面。
Next
设置、多语言与调试