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
相关产品推荐
相关产品推荐

