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

Flutter登录页点击Switch导致showModalBottomSheet重复堆叠的问题求助

问题分析与解决思路

你的核心问题在于:每次点击Switch触发状态更新后,Widget会重建,而WidgetsBinding.instance!.addPostFrameCallback会在每次重建后再次执行,导致重复弹出弹窗。

为什么会堆叠?

addPostFrameCallback是绑定在Widget构建流程上的——只要你的Widget因为Provider状态变化(比如Switch切换initdata)触发重建,这个回调就会再跑一遍。而此时setting.data还是false(直到用户点击"Sichern"按钮才会改变),所以新的弹窗会叠在旧的上面。

具体解决方案

方案1:把弹窗逻辑移到只执行一次的生命周期方法里

将当前的Widget改成StatefulWidget,然后在initState中处理弹窗逻辑——initState只会在Widget初始化时执行一次,完美避免重复触发:

class LoginScreen extends StatefulWidget {
  @override
  _LoginScreenState createState() => _LoginScreenState();
}

class _LoginScreenState extends State<LoginScreen> {
  @override
  void initState() {
    super.initState();
    // 这里用listen: false,避免在initState中监听状态变化
    final setting = Provider.of<SettingProvider>(context, listen: false);
    WidgetsBinding.instance!.addPostFrameCallback((_) {
      if (!setting.data) {
        _showPrivacyBottomSheet();
      }
    });
  }

  void _showPrivacyBottomSheet() {
    showModalBottomSheet(
      barrierColor: Colors.blueGrey.withOpacity(0.1),
      isDismissible: false,
      context: context,
      builder: (context) {
        // 用Consumer监听SettingProvider的变化,确保Switch状态实时更新
        return Consumer<SettingProvider>(
          builder: (context, setting, child) {
            return Container(
              padding: const EdgeInsets.fromLTRB(30, 10, 30, 0),
              height: 300,
              child: ListView(
                children: [
                  Center(
                    child: Text(
                      'Datenschutz',
                      style: TextStyle(
                        fontFamily: 'Caveat',
                        fontSize: 25,
                      ),
                    ),
                  ),
                  const Text(
                    'Description.',
                    style: TextStyle(fontSize: 10),
                    textAlign: TextAlign.justify,
                  ),
                  SwitchListTile(
                    title: const Text(
                      'I agree.',
                      style: TextStyle(fontSize: 10),
                    ),
                    value: setting.initdata,
                    onChanged: setting.changeInitData,
                  ),
                  const SizedBox(height: 10),
                  ElevatedButton(
                    onPressed: () {
                      if (setting.initdata) {
                        setting.changeData(true);
                        Navigator.pop(context);
                      } else {
                        Fluttertoast.showToast(
                          msg: 'Error.',
                          gravity: ToastGravity.TOP,
                        );
                      }
                    },
                    child: const Text('Sichern'),
                  ),
                ],
              ),
            );
          },
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    // 你的登录界面UI
    return Scaffold(
      // ...
    );
  }
}

方案2:给Provider加一个"已显示弹窗"的标志位

如果你的场景需要更灵活的控制(比如用户退出后重新进入还要显示),可以在SettingProvider里加一个标志位,确保弹窗只显示一次:

class SettingProvider extends ChangeNotifier {
  bool data = false;
  bool initdata = false;
  bool _hasShownPrivacyModal = false; // 新增标志位

  void changeData(bool value) {
    data = value;
    notifyListeners();
  }

  void changeInitData(bool value) {
    initdata = value;
    notifyListeners();
  }

  bool get shouldShowPrivacyModal => !data && !_hasShownPrivacyModal;

  void markModalAsShown() {
    _hasShownPrivacyModal = true;
    notifyListeners();
  }
}

然后在弹窗逻辑里判断这个标志位,显示后标记为已显示:

// 在合适的地方(比如initState或didChangeDependencies)
if (setting.shouldShowPrivacyModal) {
  setting.markModalAsShown();
  showModalBottomSheet(...);
}

关键注意点

  • 永远不要在build方法里直接执行弹窗、网络请求这类有副作用的操作——build会因为各种原因(状态变化、屏幕旋转、父Widget重建)频繁触发,很容易导致重复执行。
  • 使用Provider时,在只需要获取数据不需要监听变化的场景(比如initState),记得加listen: false,避免不必要的重建。

内容的提问来源于stack exchange,提问作者Jendrik Peters

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 03:18:12