Flutter系统返回键失效:如何实现返回上一页而非退出应用?
我代码中的BackButtonInterceptor无法正常工作,尝试过PopScope也没解决问题。我不想退出应用,只想返回上一页,请问需要做什么?比如是否需要配置父路由?导航栏的返回图标可以正常工作,但系统返回键完全没效果。
相关代码
class Checklist extends StatefulWidget { const Checklist({super.key}); @override _ChecklistState createState() => _ChecklistState(); } class _ChecklistState extends State<Checklist> { @override void initState() { super.initState(); Provider.of<checkListProvider>(context, listen: false).loadData(); BackButtonInterceptor.add(_onBackPressed); } @override void dispose() { BackButtonInterceptor.remove(_onBackPressed); super.dispose(); } bool _onBackPressed(bool stopDefaultButtonEvent, RouteInfo info) { _showExitDialog(); return true; } Future<void> _showExitDialog() async { final shouldPop = await showDialog<bool>( context: context, builder: (context) => AlertDialog( title: const Text('Confirm Exit'), content: const Text('Do you want to go back to the previous screen?'), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), child: const Text('Cancel'), ), TextButton( onPressed: () { Navigator.of(context).pop(true); GoRouter.of(context).pop(); // Go back to the previous route }, child: const Text('Yes'), ), ], ), ); if (shouldPop ?? false) { GoRouter.of(context).pop(); } } @override Widget build(BuildContext context) { final pageState = Provider.of<PageState>(context); final titleProvider = Provider.of<PageTitle>(context); final title = titleProvider.title.isNotEmpty ? titleProvider.title : 'Default Title'; final checkProvider = Provider.of<checkListProvider>(context); return Scaffold( backgroundColor: Colors.teal[50], appBar: AppBar( backgroundColor: Colors.teal[50], title: Center(child: Text(title)), leading: IconButton( icon: const Icon(Icons.chevron_left_outlined), onPressed: () { pageState.setRoute('/help-center'); GoRouter.of(context).pop('/help-center'); }, ), ), body: checkProvider.isLoading ? const Center(child: CircularProgressIndicator()) : checkProvider.errorMessage.isNotEmpty ? Center(child: Text(checkProvider.errorMessage)) : ListView( padding: const EdgeInsets.all(10), children: [ ExpansionTile( title: const Text('Completed'), children: checkProvider.completed .map((item) => ListTile( title: Text(item['title']), subtitle: Text(item['description']), )) .toList(), ), ExpansionTile( title: const Text('Next'), children: checkProvider.next .map( (item) => CheckboxListTile( checkColor: Colors.teal[500], checkboxShape: const CircleBorder(), title: Text(item['title']), subtitle: Text(item['description']), value: item['completed'], onChanged: (val) { if (val == true) { checkProvider.toogleComplete(item, 'next'); } }, ), ) .toList(), ), ExpansionTile( title: const Text('Upcoming'), children: checkProvider.upcoming .map( (item) => CheckboxListTile( checkColor: Colors.teal[500], checkboxShape: const CircleBorder(), title: Text(item['title']), subtitle: Text(item['description']), value: item['completed'], onChanged: (val) { if (val == true) { checkProvider.toogleComplete( item, 'upcoming'); } }, ), ) .toList(), ), ], ), ); } }
解决方案
核心原因
你使用了GoRouter管理路由,而BackButtonInterceptor是针对原生Navigator的拦截逻辑,GoRouter会接管系统返回键的处理,导致原生拦截器失效。PopScope需要正确配合GoRouter的路由栈逻辑才能生效。
具体修改步骤
- 移除BackButtonInterceptor相关代码:删除
initState和dispose中拦截器的注册、移除代码。 - 用PopScope包裹Scaffold:在build方法中,将Scaffold嵌套进PopScope组件,自定义返回行为。
- 修复对话框中的重复pop问题:当前代码中Yes按钮和后续判断会执行两次
GoRouter.of(context).pop(),导致路由栈异常。
修改后的代码片段
@override Widget build(BuildContext context) { final pageState = Provider.of<PageState>(context); final titleProvider = Provider.of<PageTitle>(context); final title = titleProvider.title.isNotEmpty ? titleProvider.title : 'Default Title'; final checkProvider = Provider.of<checkListProvider>(context); return PopScope( canPop: false, // 阻止默认返回行为 onPopInvoked: (didPop) async { if (!didPop) { await _showExitDialog(); } }, child: Scaffold( // 原Scaffold内容保持不变 backgroundColor: Colors.teal[50], appBar: AppBar( backgroundColor: Colors.teal[50], title: Center(child: Text(title)), leading: IconButton( icon: const Icon(Icons.chevron_left_outlined), onPressed: () { pageState.setRoute('/help-center'); GoRouter.of(context).pop(); }, ), ), body: checkProvider.isLoading ? const Center(child: CircularProgressIndicator()) : checkProvider.errorMessage.isNotEmpty ? Center(child: Text(checkProvider.errorMessage)) : ListView( padding: const EdgeInsets.all(10), children: [ ExpansionTile( title: const Text('Completed'), children: checkProvider.completed .map((item) => ListTile( title: Text(item['title']), subtitle: Text(item['description']), )) .toList(), ), ExpansionTile( title: const Text('Next'), children: checkProvider.next .map( (item) => CheckboxListTile( checkColor: Colors.teal[500], checkboxShape: const CircleBorder(), title: Text(item['title']), subtitle: Text(item['description']), value: item['completed'], onChanged: (val) { if (val == true) { checkProvider.toogleComplete(item, 'next'); } }, ), ) .toList(), ), ExpansionTile( title: const Text('Upcoming'), children: checkProvider.upcoming .map( (item) => CheckboxListTile( checkColor: Colors.teal[500], checkboxShape: const CircleBorder(), title: Text(item['title']), subtitle: Text(item['description']), value: item['completed'], onChanged: (val) { if (val == true) { checkProvider.toogleComplete( item, 'upcoming'); } }, ), ) .toList(), ), ], ), ), ); } // 修复后的_showExitDialog方法 Future<void> _showExitDialog() async { final shouldPop = await showDialog<bool>( context: context, builder: (context) => AlertDialog( title: const Text('Confirm Exit'), content: const Text('Do you want to go back to the previous screen?'), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), child: const Text('Cancel'), ), TextButton( onPressed: () { Navigator.of(context).pop(true); }, child: const Text('Yes'), ), ], ), ); if (shouldPop ?? false) { pageState.setRoute('/help-center'); GoRouter.of(context).pop(); } }
额外检查项
- 确认当前页面是通过
GoRouter.push()进入的,而非GoRouter.replace()或作为初始路由,确保路由栈中存在可返回的上一页。 - 检查GoRouter的路由配置,确保
/help-center是当前页面的父路由,或路由栈结构正确。
内容的提问来源于stack exchange,提问作者Ikram Ico
相关产品推荐
相关产品推荐

