Flutter中ChangeNotifier通知时显示BottomSheet的正确实现方式
基于ChangeNotifierProvider实现等级提升弹窗的正确方式
你遇到的两个问题根源分别是:
- Consumer的builder方法仅用于构建UI,在其中调用
showBottomSheet会触发UI状态的同步变化,导致BuildContext异常或重复构建崩溃。 - 直接监听ChangeNotifier的全局通知,会在任何变量更新触发
notifyListeners时执行弹窗逻辑,且未重置触发变量导致重复弹出。
以下是两种可行的解决方案:
方案一:优化全局监听器逻辑
通过在页面的initState中添加监听器,同时做状态校验和变量重置,避免重复触发:
class YourPage extends StatefulWidget { @override State<YourPage> createState() => _YourPageState(); } class _YourPageState extends State<YourPage> { late YourModel _model; @override void initState() { super.initState(); // 获取模型实例,listen设为false避免不必要的重建 _model = Provider.of<YourModel>(context, listen: false); _model.addListener(_onLevelUp); } void _onLevelUp() { final level = _model.leveledUpLevel; if (level != null) { // 仅在当前页面处于活跃状态时弹窗 if (ModalRoute.of(context)?.isCurrent ?? false) { showBottomSheet( context: context, builder: (_) => Container( padding: EdgeInsets.all(16), child: Text("恭喜升级到$level级!"), ), ); // 重置触发变量,防止后续notifyListeners重复触发 _model.resetLeveledUpLevel(); } } } @override void dispose() { _model.removeListener(_onLevelUp); super.dispose(); } @override Widget build(BuildContext context) { // 页面UI构建逻辑 return Scaffold(body: Center(child: Text("当前页面"))); } }
对应的模型需要添加重置方法:
class YourModel extends ChangeNotifier { int? leveledUpLevel; void triggerLevelUp(int level) { leveledUpLevel = level; notifyListeners(); } void resetLeveledUpLevel() { leveledUpLevel = null; // 无需notifyListeners,仅重置状态即可 } }
方案二:使用专用ValueNotifier监听单个变量
如果只需要监听leveledUpLevel的变化,在模型中新增一个ValueNotifier,实现精准监听:
class YourModel extends ChangeNotifier { final ValueNotifier<int?> _levelUpNotifier = ValueNotifier(null); // 暴露给页面监听的notifier ValueNotifier<int?> get levelUpNotifier => _levelUpNotifier; void triggerLevelUp(int level) { _levelUpNotifier.value = level; // 其他业务逻辑触发全局通知(如果需要) notifyListeners(); } void resetLevelUp() { _levelUpNotifier.value = null; } }
页面中监听这个专用的ValueNotifier:
class YourPage extends StatefulWidget { @override State<YourPage> createState() => _YourPageState(); } class _YourPageState extends State<YourPage> { late YourModel _model; @override void initState() { super.initState(); _model = Provider.of<YourModel>(context, listen: false); _model.levelUpNotifier.addListener(_onLevelUp); } void _onLevelUp() { final level = _model.levelUpNotifier.value; if (level != null && ModalRoute.of(context)?.isCurrent ?? false) { showBottomSheet( context: context, builder: (_) => Container( padding: EdgeInsets.all(16), child: Text("恭喜升级到$level级!"), ), ); _model.resetLevelUp(); } } @override void dispose() { _model.levelUpNotifier.removeListener(_onLevelUp); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold(body: Center(child: Text("当前页面"))); } }
方案选择
- 若仅需监听单个变量的变化,方案二更高效,避免了全局通知的干扰。
- 若需要结合其他全局状态的变化逻辑,方案一在重置变量后也能稳定工作。
内容的提问来源于stack exchange,提问作者Patola
相关产品推荐
相关产品推荐

