资讯动态

pure_live 共享文本编辑弹窗生命周期与布局审计实践:从 TextEditingController 越界到响应式治理

发布时间:2026/10/15 6:05:12 来源:尧图企业网站定制
音视频直播移动开发【免费下载链接】pure_live纯粹直播:哔哩哔哩/虎牙/斗鱼/快手/抖音/网易cc/YY直播/Twitch直播/SOOP直播/M38自定义源应有尽有。项目地址https://gitcode.com/gh_mirrors/pur/pure_live点击查看免费下载本文以 pure_live 仓库中 docs/SHARED_EDIT_DIALOG_LIFECYCLE_AND_LAYOUT_AUDIT_2026_09_13.md 审计记录为骨架围绕共享文本编辑弹窗AppPromptDialogs.showEditTextDialog展开先讲清 Flutter 路由结果先于退出动画卸载、导致控制器已释放仍被访问的根因再落到 pure_live 的_EditTextDialog收敛实现、468 宽度上限、16/20 视口边距、窄屏/大字号纵向动作与具名滚动面最后给出确定性验证方法与可复现的验收边界。读者可据此掌握一套弹窗类自有状态 生命周期对齐 响应式视口的 Flutter 通用治理方案并直接在仓库源码中核对每一条结论。一、问题背景弹窗路由生命周期与控制器所有权的错位pure_live 是一个基于 Flutter 的多平台直播客户端。在账号认证、IPTV 导入等场景中大量功能需要复用同一个带输入框的编辑弹窗。审计基线d7f70c2e的旧实现存在一个典型缺陷控制器创建位置错误旧实现由静态方法在弹窗路由外部创建TextEditingController并在Get.dialog返回Future完成后立即释放释放时机早于子树卸载Flutter 的Get.dialog路由结果会先于弹窗退出动画的子树卸载完成。也就是说点击确认后Future先返回控制器随即被dispose但此时弹窗仍处于反向动画中TextField还在帧里继续访问已经释放的控制器稳定可复现的异常该问题在 320×480、3.0 倍文字等窄屏夹具下稳定触发A TextEditingController was used after being disposed并且后续帧同时出现渲染异常与焦点级联异常——控制器越界释放不仅产生断言还会破坏整棵弹窗子树的布局与焦点状态。除了生命周期问题旧弹窗还有布局上的硬伤窄屏和大字号下固定 420 宽的输入区、45 行输入区与独立动作区缺少统一的视口边界动作按钮可能被挤出屏幕安全区域。从源码结构看这个问题的本质是谁拥有TextEditingController这一所有权问题没有界定清楚控制器既不在 State 内也不在路由子树内导致释放时机与TextField的实际生命周期脱节。二、修订方案_EditTextDialog自有状态收敛修订提交85ca7a73的核心思路是把共享编辑器收敛为自有状态的 StatefulWidget。在 pure_live 源码中该实现位于 lib/core/widgets/app_prompt_dialogs.dartstatic FutureString? showEditTextDialog( String content, { String title , String? hintText, String confirm , String cancel , }) async { return Get.dialogString( _EditTextDialog(initialValue: content, title: title, hintText: hintText, confirm: confirm, cancel: cancel), ); }公开入口保持FutureString?的返回契约不变确认后逐字返回输入内容取消返回null。调用方无需关心弹窗内部实现只需处理null与字符串两种结果这一契约保证了账号认证、IPTV 等既有调用链零改动接入。2.1 控制器生命周期对齐initState 创建、dispose 释放_EditTextDialogState将控制器的创建与释放完全对齐到 State 生命周期class _EditTextDialogState extends State_EditTextDialog { late final TextEditingController _controller; override void initState() { super.initState(); _controller TextEditingController(text: widget.initialValue); } override void dispose() { _controller.dispose(); super.dispose(); } // ... }创建控制器在initState中创建并以widget.initialValue初始化文本——首次显示即携带既有内容符合编辑既有配置的语义释放控制器只在弹窗子树真正卸载的dispose中释放而非在Get.dialog的Future返回后立即释放。这从根本上消除了反向动画期间访问已释放控制器的窗口关闭路由确认与取消都使用弹窗自身的BuildContext调用Navigator.of(context).pop(...)关闭的是构建动作的那个弹窗路由而不是通过全局Get.context猜测 Navigator。2.2 同样的治理范式与_UserAgentDialog的一致性值得注意的是同一仓库的 IPTV 页面中还存在一个私有对话框 lib/domains/iptv/presentation/iptv_page.dart其_UserAgentDialog同样采用initState创建控制器、dispose释放的范式并且源码注释直接写着// Draft fields belong to the route subtree, not the earlier pop-result Future.这句注释准确概括了本审计的核心原则草稿状态Draft必须归属于路由子树而非提前完成的 pop-result Future。这也是 Flutter 弹窗类组件避免已释放控制器被访问的统一解法。三、布局治理宽度上限、视口边距与响应式动作修订为弹窗确立了明确的视口边界体系全部体现在_EditTextDialogState.build中lib/core/widgets/app_prompt_dialogs.dart3.1 宽度与边距return Dialog( insetPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20), child: ConstrainedBox( constraints: BoxConstraints(maxWidth: 468, maxHeight: dialogHeight), child: SizedBox(width: 468, ...), ), );弹窗宽度上限 468旧实现为固定 420四周保留16 水平 / 20 垂直的窗口边距可用高度成为明确上限final availableHeight media.size.height;、final dialogHeight availableHeight 40 ? availableHeight - 40 : availableHeight;即弹窗高度不超过视口高度减去上下 40 边距避免内容溢出屏幕输入区保持minLines: 4, maxLines: 5并开启autofocus: true与旧行为一致。3.2 窄屏与大字号下的纵向动作final largeText media.textScaler.scale(1) 1.6; final stackActions media.size.width 420 || largeText;当窗口宽度小于 420或文字缩放达到 1.6 倍时弹窗切换为动作区改为纵向全宽排列ColumnCrossAxisAlignment.stretch取消按钮在上、确认按钮在下两个按钮撑满整行保证大字号下可读、可点内容进入具名滚动面SingleChildScrollView带key: const ValueKeyString(shared-edit-text-scroll)标题与 45 行编辑区共用弹性滚动区域大字号专用全高布局Flexible(fit: largeText ? FlexFit.tight : FlexFit.loose, ...)让编辑区在有限空间内按可用视口分配标题与输入区滚动动作区保持可见动作区用Divider与编辑区隔开并包裹SafeArea(top: false)避免系统手势区遮挡按钮。3.3 普通窗口保持紧凑常规尺寸窗口不扩散大字号专用全高布局保持紧凑内容高度与右对齐横向动作Align(alignment: Alignment.centerRight)Wrap间距 8桌面常规尺寸下观感不变确认按钮使用FilledButton并以主题色theme.colorScheme.primary填充取消按钮使用TextButton视觉层级清晰。四、真实调用链两处入口如何接入共享弹窗AppPromptDialogs.showEditTextDialog的返回值契约确认返回文本、取消返回null在实际调用链中被严格消费4.1 Firebase 邮箱认证Windows GitHub 登录粘贴入口在 lib/domains/account/presentation/auth/components/firebase_email_auth.dart 中Windows 平台走浏览器完成 GitHub OAuth随后用共享弹窗接收粘贴回来的授权链接final pasteText await AppPromptDialogs.showEditTextDialog( , title: i18n(github_login), hintText: i18n(paste_auth_link), ); if (pasteText null || pasteText.trim().isEmpty) { return; } const prefix purelive://auth?credential; final text pasteText.trim(); if (!text.startsWith(prefix)) { throw invalid link; }取消语义pasteText null直接return不发请求——取消即中止登录流程空输入语义trim().isEmpty同样直接返回与取消等价校验语义非空输入按purelive://auth?credential前缀校验再Uri.decodeComponent解出accessToken走signInWithCredential。审计中认证页粘贴入口即指这条链路。4.2 IPTV 管理页网络导入输入lib/domains/iptv/presentation/iptv_page.dart 中showEditTextDialog({required bool isEpg})通过showDialog弹出_NetworkImportDialogbarrierDismissible: false禁止误触遮罩关闭提交时经importFromNetworkUrl(url, name)走IptvImportManager/EpgImportManager的网络导入。该页还维护_networkDialogOpen与_localImporting / _networkImporting状态位防止重复触发——这与共享弹窗生命周期 状态所有权的治理思路一脉相承。五、确定性证据与门禁结果审计采用有效红灯 → 修订 → 直接回归 → 最终门禁的确定性验证链阶段产物/数据结果有效红灯local-artifacts/build-records/20260913T104947924Z-quality-focused.json旧实现0/2 PASS确认退出稳定触发A TextEditingController was used after being disposed后续帧出现渲染与焦点级联异常直接回归320×480、3.0 倍文字夹具先锁定控制器生命周期已恢复再调整可用高度与动作排列中间布局诊断仅用于定位不作为完成证据最终门禁local-artifacts/build-records/20260913T121448341Z-quality-focused.json覆盖共享编辑器、Firebase 邮箱认证生命周期与 Get 根 Overlay共14/14 PASS静态检查同一最终门禁内仅执行一次全库flutter analyzeNo issues found格式、仓库审计与差异检查通过审计还特别记录了过程约束未构建 Android/Windows 候选未执行 ADB、设备 UI、Windows GUI、Computer Use 或外部网络请求Astra Light 使用 0 次门禁结束采样时共享机器仍有 1 个其他任务的活跃重型进程本批自有测试进程已正常退出实际 ADB 命令为 0。这些记录保证了结论只针对代码与 Widget 级确定性验证不混入设备级变量。六、验收边界与后续未闭环项本轮补强的是验收组A2-01共享配置编辑、认证页粘贴入口、窄屏大字号、系统返回生命周期证据该项继续保持RUN状态。代码与 Widget 级证据已闭环但以下真实交互仍需在 Android/Windows 候选上分别执行真实键盘输入粘贴尤其 Windows GitHub 登录粘贴链路确认、取消系统返回Android 系统返回键 / 手势连续重开弹窗。此外按仓库规则Windows GUI 批次开始时只创建并复用一个 Astra Light 任务。宏观验收账本保持20 PASS / 42 RUN / 0 NR共 42 组未闭环——即大部分证据已由确定性测试锁定真实设备交互仍在逐项推进。七、实践要点小结控制器所有权归属 StateTextEditingController在initState创建、dispose释放绝不在Get.dialog/showDialog的Future返回后立即释放——路由结果先于退出动画子树卸载完成用弹窗自身BuildContext关闭所属路由不要用全局Get.context猜测 Navigator视口边界三件套insetPadding四周 16/20 边距、maxWidth宽度上限、maxHeight以屏幕可用高度为上限窄屏/大字号触发响应式动作width 420或textScaler.scale(1) 1.6时纵向全宽排列动作、内容进具名SingleChildScrollViewshared-edit-text-scroll契约稳定确认逐字返回输入内容、取消返回null调用方只需处理null与字符串两种结果。如需深入核对实现细节可直接阅读 lib/core/widgets/app_prompt_dialogs.dart共享弹窗三件套与_EditTextDialog、lib/domains/account/presentation/auth/components/firebase_email_auth.dartWindows GitHub 登录粘贴链路以及 lib/domains/iptv/presentation/iptv_page.dartIPTV 网络导入对话框与_UserAgentDialog的控制器生命周期注释。审计完整记录见 docs/SHARED_EDIT_DIALOG_LIFECYCLE_AND_LAYOUT_AUDIT_2026_09_13.md同批次的共享确认弹窗审计见 docs/SHARED_ALERT_DIALOG_LAYOUT_AND_ROUTE_AUDIT_2026_09_13.md。赞分享音视频直播移动开发【免费下载链接】pure_live纯粹直播:哔哩哔哩/虎牙/斗鱼/快手/抖音/网易cc/YY直播/Twitch直播/SOOP直播/M38自定义源应有尽有。项目地址https://gitcode.com/gh_mirrors/pur/pure_live点击查看免费下载相关推荐pure_live 通用设置倒计时弹窗审计窄屏溢出与 TextEditingController 生命周期修复pure_live 通用设置倒计时弹窗审计窄屏溢出与 TextEditingController 生命周期修复 导读 本文以 pure_live 仓库中《通用音视频直播移动开发pure_live 共享确认弹窗布局与路由重构审计窄屏大字号溢出的根治方案pure_live 共享确认弹窗布局与路由重构审计窄屏大字号溢出的根治方案 导读 本文基于 pure_live 仓库中的《共享确认与消息弹窗布局及路由审计》文音视频直播移动开发pure_live 的 IPTV 订阅源管理页布局与生命周期审计实践从 0/6 失败到 195/195 全绿pure_live 的 IPTV 订阅源管理页布局与生命周期审计实践从 0/6 失败到 195/195 全绿 IPTV/EPG 订阅源管理页 IptvMan音视频直播移动开发上一篇如何快速上手CoreWCF5分钟创建你的第一个跨平台WCF服务下一篇Electron Forge Maker 开发指南从零编写自定义分发器Maker扩展创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑