Flutter 首次启动弹窗重复弹出、输入时叠加弹窗问题
问题原因
两个异常分别来自3处代码错误:
- 持久化存值逻辑写错:
prefs.setBool(newLaunch, false)方法第一个参数要求传入字符串类型的存储键名,你传入了布尔类型的newLaunch变量,导致非首次启动的状态从来没有被正确写入本地存储,每次启动读取到的都是默认值true,自然每次都判定为首次启动。 - 弹窗触发逻辑位置错误:你把调用弹窗的
Future.delayed代码写在了build方法里,Flutter每次触发UI重建(比如输入框调用setState、页面刷新、键盘弹出)都会重新执行build方法,等于每次重建都会触发一次弹窗检查。 - 输入框输入时触发的
setState会强制页面重绘,结合上面build里的弹窗逻辑,每输入一个字符就重跑一次build、弹一次窗,最终出现弹窗层层堆叠的问题。
修复方案
- 把首次启动检查、弹窗触发的逻辑从
build方法移到initState生命周期里,保证整个页面生命周期里只执行一次检查,不会因为UI重建重复触发。 - 修正
shared_preferences的存值参数,严格按照setBool(键名, 存储值)的格式传参,确保非首次启动的状态能正确写入本地。 - 补充必要的安全判断:弹窗弹出前校验组件是否仍挂载、设置弹窗不可通过点击外部蒙层关闭,避免状态未写入就被关闭、组件销毁后调用上下文引发报错。
- 补全缺失的变量定义、控制器释放逻辑,避免其他隐藏报错。
修复后完整代码
class HomeMenu extends StatefulWidget { const HomeMenu({Key? key}) : super(key: key); @override State<HomeMenu> createState() => _HomeMenuState(); } class _HomeMenuState extends State<HomeMenu> with TickerProviderStateMixin { late TabController _controller; TextEditingController textController = TextEditingController(); String name = ''; bool newLaunch = true; Future<void> showDialogIfFirstLoaded(BuildContext thisContext) async { SharedPreferences prefs = await SharedPreferences.getInstance(); newLaunch = prefs.getBool('newLaunch') ?? true; if (newLaunch) { if (!mounted) return; showDialog( context: thisContext, barrierDismissible: false, builder: (BuildContext context) { return AlertDialog( content: TextField( controller: textController, onChanged: (value) { setState(() => name = textController.text); }, ), actions: <Widget>[ TextButton( child: const Text('OK'), onPressed: () async { await prefs.setBool('newLaunch', false); newLaunch = false; if (mounted) Navigator.pop(thisContext); }, ), ], ); }, ); } } @override void initState() { super.initState(); _controller = TabController(length: 3, vsync: this); WidgetsBinding.instance.addPostFrameCallback((timeStamp) { showDialogIfFirstLoaded(context); }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( bottom: TabBar( controller: _controller, tabs: const [ Tab(text: 'Tab1'), Tab(text: 'Tab2'), Tab(text: 'Tab3'), ], ), ), body: TabBarView( controller: _controller, children: const [ Center(child: Text('Page1')), Center(child: Text('Page2')), Center(child: Text('Page3')), ], ), ); } @override void dispose() { _controller.dispose(); textController.dispose(); super.dispose(); } }
内容的提问来源于stack exchange,提问作者Asjal Ahmad
相关产品推荐
相关产品推荐

