Flutter中BoardView二次跳转后Stateful Widget异常问题求助
问题排查:Flutter二次进入BoardView时弹窗同时弹出的问题
我运行下述Flutter代码时,首次进入BoardView Widget功能符合预期;但第二次通过_RoundInfoState进入BoardView时,电脑玩家的AlertDialog会与人类玩家的AlertDialog同时弹出。附上代码及两次进入的效果截图,请帮忙排查问题所在。
import 'package:flutter/material.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), home: MyHomePage(), ); } } class MyHomePage extends StatefulWidget { Round roundController = Round(currentIndex: -1); @override State<MyHomePage> createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { @override Widget build(BuildContext context) { return Scaffold( body: Center( child: ElevatedButton( child: const Text("New Game", style: TextStyle(fontSize: 50, color: Colors.black)), onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (context) => BoardView(roundController: widget.roundController)), ); }))); } } class Round extends ChangeNotifier { Round({required this.currentIndex}) : handNo = 0; void changeTurn() { if (currentIndex == -1) { currentIndex = 0; } else { currentIndex = currentIndex == 0 ? 1 : 0; } notifyListeners(); } int currentIndex, handNo; } class BoardView extends StatefulWidget { Round roundController; BoardView({Key? key, required this.roundController}) : super(key: key); @override _BoardViewState createState() => _BoardViewState(); } class _BoardViewState extends State<BoardView> { late List<String> players = ["Human", "Computer"]; late bool showButton; void _playerTakesTurn() { if (players[widget.roundController.currentIndex] == "Human") { showDialogPopUp( context, "Human Player's Turn", "It is time for you to take your turn.", widget.roundController.handNo, widget.roundController); } else { showDialogPopUp( context, "Computer Player's Tile Placement", "The computer took their turn", widget.roundController.handNo, widget.roundController); } } @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) => _playerTakesTurn()); widget.roundController.addListener(_playerTakesTurn); } @override void dispose() { widget.roundController.removeListener(_playerTakesTurn); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.teal[400], ); } } Future<void> showDialogPopUp(BuildContext context, String title, String text, int handNo, Round roundController) { return showDialog<String>( context: context, builder: (BuildContext context) { return AlertDialog(title: Text(title), content: Text(text), actions: [ TextButton( child: const Text("OK"), onPressed: () { Navigator.of(context).pop(); roundController.changeTurn(); if (handNo == 2) { Navigator.push( context, MaterialPageRoute( builder: (context) => RoundInfo( roundController: roundController, ))); } else { roundController.handNo++; } }, ) ]); }); } class RoundInfo extends StatefulWidget { final Round roundController; const RoundInfo({Key? key, required this.roundController}) : super(key: key); @override State<RoundInfo> createState() => _RoundInfoState(); } class _RoundInfoState extends State<RoundInfo> { @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.greenAccent[700], body: Column(children: [ ElevatedButton( onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (context) => BoardView( roundController: widget.roundController))); }, style: ElevatedButton.styleFrom( backgroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(50), ), ), child: const Text('Proceed', style: TextStyle( color: Colors.black, fontSize: 35, fontWeight: FontWeight.bold))), ])); } }
首次进入BoardView的效果:
第二次通过_RoundInfoState进入BoardView的效果:
问题原因
- 全局Round实例状态残留:整个应用共享同一个
Round实例,第一次游戏结束后,currentIndex和handNo的状态未重置,第二次进入BoardView时直接复用旧状态,导致弹窗逻辑异常触发。 - 弹窗触发逻辑重复:二次进入BoardView时,
initState中的addPostFrameCallback会调用一次_playerTakesTurn,弹出对应玩家的弹窗;而弹窗点击OK后调用changeTurn触发监听器,再次调用_playerTakesTurn,导致两个弹窗连续弹出(视觉上类似同时出现)。 - 索引越界隐患:当
currentIndex=-1时,直接访问players[currentIndex]会抛出RangeError,这也是代码中的潜在问题。
解决方案
方案1:每次进入新游戏创建新的Round实例
修改MyHomePage和RoundInfo的按钮点击逻辑,每次进入BoardView时生成全新的Round实例,彻底避免状态残留:
// MyHomePage中的按钮点击 onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (context) => BoardView(roundController: Round(currentIndex: -1))), ); } // RoundInfo中的按钮点击 onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (context) => BoardView(roundController: Round(currentIndex: -1)))); }
方案2:给Round添加状态重置方法
如果需要保留同一个Round实例,给Round类添加重置方法,在进入BoardView时重置状态:
class Round extends ChangeNotifier { Round({required this.currentIndex}) : handNo = 0; void changeTurn() { if (currentIndex == -1) { currentIndex = 0; } else { currentIndex = currentIndex == 0 ? 1 : 0; } notifyListeners(); } // 新增重置方法 void reset() { currentIndex = -1; handNo = 0; notifyListeners(); } int currentIndex, handNo; }
然后在BoardView的initState中调用重置:
@override void initState() { super.initState(); widget.roundController.reset(); WidgetsBinding.instance.addPostFrameCallback((_) => _playerTakesTurn()); widget.roundController.addListener(_playerTakesTurn); }
方案3:修复索引越界问题
在_playerTakesTurn中先处理currentIndex=-1的情况,避免数组越界报错:
void _playerTakesTurn() { if (widget.roundController.currentIndex == -1) { widget.roundController.changeTurn(); return; } if (players[widget.roundController.currentIndex] == "Human") { showDialogPopUp( context, "Human Player's Turn", "It is time for you to take your turn.", widget.roundController.handNo, widget.roundController); } else { showDialogPopUp( context, "Computer Player's Tile Placement", "The computer took their turn", widget.roundController.handNo, widget.roundController); } }
额外优化
使用Navigator.pushReplacement代替Navigator.push,避免导航栈中积累多个BoardView实例:
// RoundInfo中的按钮点击修改为 Navigator.pushReplacement( context, MaterialPageRoute( builder: (context) => BoardView(roundController: widget.roundController)));
内容的提问来源于stack exchange,提问作者نظامی گنجوی
相关产品推荐
相关产品推荐

