基于Flutter BLoC实现未保存表单跳转时的弹窗确认功能
Flutter BLoC模式下表单页面未保存时的离开确认实现
问题描述
我正在用BLoC模式开发Flutter应用,其中一个页面包含表单。需要实现:当用户未保存表单就尝试跳转到其他页面时,弹出确认弹窗,提供保存更改的选项。目前已有检查表单保存状态的基础架构,下面是维护保存状态的Cubit代码:
class AppbarSaveActionCubit extends Cubit<AppbarSaveActionState> { AppbarSaveActionCubit() : super(AppbarSaveActionState.initial()); // 从底部栏触发子组件保存操作 void saveAction(){ final newState = state.copyWith(saveAction: true); emit(newState); // 重置Cubit状态 emit(AppbarSaveActionState.initial()); } // 初始化保存状态 void saveActionInitState(){ final newState = state.copyWith(saveAction: false); emit(newState); } // 从底部栏触发子组件返回操作 void backPageAction(){ final newState = state.copyWith(backAction: true); emit(newState); // 重置Cubit状态 emit(AppbarSaveActionState.initial()); } }
实现方案
1. 完善状态与Cubit逻辑
首先需要在状态中添加未保存修改标记,用来追踪表单是否有未提交的更改,同时调整Cubit方法,避免状态被立即重置导致监听失效:
修改状态类
class AppbarSaveActionState { final bool hasUnsavedChanges; // 新增:标记是否有未保存修改 final bool saveAction; final bool backAction; AppbarSaveActionState({ required this.hasUnsavedChanges, required this.saveAction, required this.backAction, }); // 初始状态:无未保存修改,无动作触发 static AppbarSaveActionState initial() { return AppbarSaveActionState( hasUnsavedChanges: false, saveAction: false, backAction: false, ); } AppbarSaveActionState copyWith({ bool? hasUnsavedChanges, bool? saveAction, bool? backAction, }) { return AppbarSaveActionState( hasUnsavedChanges: hasUnsavedChanges ?? this.hasUnsavedChanges, saveAction: saveAction ?? this.saveAction, backAction: backAction ?? this.backAction, ); } }
更新Cubit方法
class AppbarSaveActionCubit extends Cubit<AppbarSaveActionState> { AppbarSaveActionCubit() : super(AppbarSaveActionState.initial()); // 标记表单有未保存修改(表单输入时调用) void markAsDirty() { emit(state.copyWith(hasUnsavedChanges: true)); } // 标记表单已保存(保存成功后调用) void markAsSaved() { emit(state.copyWith(hasUnsavedChanges: false)); } // 触发保存操作 void triggerSaveAction() { emit(state.copyWith(saveAction: true)); // 保存完成后调用markAsSaved重置状态 } // 触发返回操作 void triggerBackAction() { emit(state.copyWith(backAction: true)); } // 重置动作状态 void resetActionState() { emit(state.copyWith(saveAction: false, backAction: false)); } }
2. 拦截页面离开操作
针对两种离开场景分别处理:系统返回键、自定义导航按钮。
处理系统返回键(WillPopScope)
在表单页面的外层包裹WillPopScope,拦截系统返回事件:
class FormPage extends StatelessWidget { @override Widget build(BuildContext context) { return BlocConsumer<AppbarSaveActionCubit, AppbarSaveActionState>( listener: (context, state) { // 监听保存动作,执行实际保存逻辑 if (state.saveAction) { _performSave(context); context.read<AppbarSaveActionCubit>().resetActionState(); } // 监听返回动作,执行导航 if (state.backAction) { Navigator.of(context).pop(); context.read<AppbarSaveActionCubit>().resetActionState(); } }, builder: (context, state) { return WillPopScope( onWillPop: () async { // 检查是否有未保存修改 if (state.hasUnsavedChanges) { return _showLeaveConfirmationDialog(context); } return true; // 允许直接返回 }, child: Scaffold( appBar: AppBar( leading: IconButton( icon: Icon(Icons.arrow_back), onPressed: () => _handleCustomBack(context), ), ), body: _buildForm(context), ), ); }, ); } // 构建表单,输入时调用markAsDirty Widget _buildForm(BuildContext context) { return TextField( onChanged: (value) { context.read<AppbarSaveActionCubit>().markAsDirty(); }, // 其他表单字段... ); } // 处理自定义返回按钮 void _handleCustomBack(BuildContext context) { final cubit = context.read<AppbarSaveActionCubit>(); if (cubit.state.hasUnsavedChanges) { _showLeaveConfirmationDialog(context).then((shouldLeave) { if (shouldLeave) { cubit.triggerBackAction(); } }); } else { cubit.triggerBackAction(); } } // 执行表单保存逻辑 void _performSave(BuildContext context) { // 这里写实际的表单保存代码(比如提交API、数据库存储) // 保存成功后标记为已保存 context.read<AppbarSaveActionCubit>().markAsSaved(); } // 显示离开确认弹窗 Future<bool> _showLeaveConfirmationDialog(BuildContext context) async { final result = await showDialog<bool>( context: context, builder: (context) => AlertDialog( title: Text('确认离开?'), content: Text('您有未保存的更改,是否保存后再离开?'), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), // 取消,不离开 child: Text('取消'), ), TextButton( onPressed: () { // 保存并离开 context.read<AppbarSaveActionCubit>().triggerSaveAction(); Navigator.of(context).pop(true); }, child: Text('保存并离开'), ), TextButton( onPressed: () => Navigator.of(context).pop(true), // 不保存直接离开 child: Text('不保存离开'), ), ], ), ); return result ?? false; } }
3. 关键逻辑说明
- 状态追踪:通过
hasUnsavedChanges字段实时记录表单修改状态,用户输入时调用markAsDirty(),保存成功后调用markAsSaved()。 - 导航拦截:
WillPopScope处理系统返回键,自定义按钮则在点击时先检查状态。 - 弹窗交互:弹窗提供三个选项,根据用户选择执行保存后离开、直接离开或取消操作,确保用户确认后再执行导航。
内容的提问来源于stack exchange,提问作者rasal
相关产品推荐
相关产品推荐

