You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的效果:
二次进入效果


问题原因

  1. 全局Round实例状态残留:整个应用共享同一个Round实例,第一次游戏结束后,currentIndex和handNo的状态未重置,第二次进入BoardView时直接复用旧状态,导致弹窗逻辑异常触发。
  2. 弹窗触发逻辑重复:二次进入BoardView时,initState中的addPostFrameCallback会调用一次_playerTakesTurn,弹出对应玩家的弹窗;而弹窗点击OK后调用changeTurn触发监听器,再次调用_playerTakesTurn,导致两个弹窗连续弹出(视觉上类似同时出现)。
  3. 索引越界隐患:当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,提问作者نظامی گنجوی

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 21:32:02