unified_popups 2.0.0
unified_popups: ^2.0.0 copied to clipboard
A unified popups SDK for Flutter, providing a flexible way to show toasts, dialogs, and other custom popups.
unified_popups #
Flutter 的统一全局弹窗系统。Toast、Loading、Confirm、Date、Sheet、FlowSheet、 Menu、DropMenu 和 Custom 共用一个 Runtime、一套冲突规则和一套生命周期。
v2 的公开创建形式只有一种:
Pop.xxx(Config) -> PopupOpenResult<T>
不需要 BuildContext、Popup 专用 navigatorKey 或页面间传递 Controller。
初始化 #
MaterialApp(
navigatorObservers: [Pop.routeObserver],
builder: Pop.hostBuilder,
home: const HomePage(),
);
首次 Host 挂载前发起的 Popup 会暂存,Host 就绪后显示。
先理解返回模型 #
Pop.xxx(config) 本身是同步调用,始终返回 PopupOpenResult<T>。await 不决定
返回的是结果还是 Handle;真正的区别是调用方从 PopupOpenResult 读取什么。
| 目的 | 写法 | 得到什么 |
|---|---|---|
| 不关心后续 | Pop.toast(config) |
忽略打开决策 |
| 等待业务值 | await Pop.confirm(config).result |
bool? |
| 外部控制 | Pop.loading(config).requireHandle() |
PopupHandle<void> |
| 处理冲突 | final opened = Pop.menu(config) |
PopupOpenResult<T> |
Pop.confirm(config)
│
▼
PopupOpenResult<bool>
├─ .result ──────────> Future<bool?>
├─ .requireHandle() ─> PopupHandle<bool>
├─ .handleOrNull ────> PopupHandle<bool>?
└─ switch ───────────> opened / updated / rejected / toggled
普通业务结果:
final confirmed = await Pop.confirm(
const ConfirmConfig(
title: '删除记录',
content: '删除后无法恢复。',
confirmAction: ConfirmAction.text('删除'),
cancelAction: ConfirmAction.text('取消'),
),
).result;
外部命令式控制:
final handle = Pop.loading(
const LoadingConfig.text('上传中'),
).requireHandle();
await handle.dismiss();
.result 是便捷但有损的业务值:用户取消、外部关闭、冲突拒绝或 toggle 都可能得到
null。需要准确原因时读取 handle.outcome;冲突可能合法发生时使用
handleOrNull,不要使用可能抛出 StateError 的 requireHandle()。
推荐在 App 内再封装一层 #
SDK 的 Config 负责完整能力;真实 App 通常还需要统一视觉、中文文案、埋点、错误
处理和默认策略。建议业务页面依赖自己的 AppPop:
abstract final class AppPop {
static void success(String message) {
Pop.toast(
ToastConfig.text(message, type: ToastType.success),
);
}
static Future<bool> confirm({
required String title,
required String content,
}) async {
return await Pop.confirm(
ConfirmConfig(
title: title,
content: content,
confirmAction: const ConfirmAction.text('确定'),
cancelAction: const ConfirmAction.text('取消'),
),
).result ??
false;
}
}
业务使用:
final confirmed = await AppPop.confirm(
title: '删除记录',
content: '删除后无法恢复',
);
Example 中 FitPulse 产品区使用 AppPop,API Lab 保留原始 Pop.xxx(Config),分别
展示真实项目封装和 SDK 完整契约。
Builder Handle #
Sheet、Menu、Custom 的 Builder 会自动获得 Handle,不需要 requireHandle():
final selected = await Pop.sheet<String>(
SheetConfig<String>(
builder: (context, handle) => Column(
children: [
ListTile(
title: const Text('复制'),
onTap: () => handle.complete('copy'),
),
TextButton(
onPressed: handle.dismiss,
child: const Text('取消'),
),
],
),
),
).result;
complete(value):业务正常完成并携带结果。dismiss():取消或无结果关闭。result:只返回 nullable value。outcome:返回 value 与准确PopupDismissReason。dismissed:退出动画完成、视觉节点移除后完成。
Loading 与 until #
Loading 的载荷构造互斥且明确:
const LoadingConfig.indicator();
const LoadingConfig.text('提交中');
const LoadingConfig.content(MyLoadingContent());
PopupLifetime.until 在 Future 成功或失败 settled 时都会关闭 Popup,业务异常仍由
业务调用方处理:
final request = saveData();
Pop.loading(
LoadingConfig.text(
'保存中',
lifetime: PopupLifetime.until(request),
),
);
await request;
默认 Loading 使用全局 key 与 updateExisting,重复调用更新同一个 Entry 和 Handle。
Menu 与 DropMenu #
final anchor = PopupAnchorController();
PopupAnchor(
controller: anchor,
child: IconButton(
icon: const Icon(Icons.more_horiz),
onPressed: () async {
final action = await Pop.menu<String>(
MenuConfig<String>(
anchor: anchor,
builder: (context, handle) => ListTile(
title: const Text('编辑'),
onTap: () => handle.complete('edit'),
),
),
).result;
},
),
);
Menu 默认使用透明、可点击关闭的 Barrier,并阻止底层滚动。只有需要底层滚动且菜单
跟随 Anchor 时才传 PopupBarrierConfig.hidden()。
DropMenu 使用 DropMenu.single 或 DropMenu.nested;标准 DropMenu 默认全局
replaceExisting。
批量管理 #
await Pop.dismissTop();
await Pop.dismissChannel(PopupChannel.sheet);
await Pop.dismissTags({'network'});
await Pop.dismissAll();
PopupBehaviorConfig 的 channel 由具体能力固定。业务只配置 key、tags、冲突、路由
和返回策略。已有 Behavior 需要清空 key 时使用 copyWith(clearKey: true)。
文档 #
运行 Example:
cd example
flutter run