使用Riverpod的Notifier时BlurBottomNav UI不更新问题求助
我正在开发一个包含4个页面的Flutter示例应用,自定义了BlurBottomNav底部导航栏。使用PageView和Riverpod(版本flutter_riverpod: ^2.1.1)切换页面或点击BlurBottomNav按钮时,页面可正常切换,但BlurBottomNav的UI无法更新。
已尝试的方案:
- 将NavButton从无状态组件改为Consumer并使用
ref.watch - 用StateNotifier替代Notifier
更新说明:使用ChangeNotifier搭配ChangeNotifierProvider时代码可正常运行,但换成Notifier后问题依旧。
相关代码片段
pageview_widget.dart
import 'package:demo/src/constants/nav_items.dart'; import 'package:demo/src/features/categories/presentation/categories_page.dart'; import 'package:demo/src/features/favourites/presentation/fav_page.dart'; import 'package:demo/src/features/pageview/data/pageview_service.dart'; import 'package:demo/src/features/pageview/presentation/bottom_nav/bottom_nav.dart'; import 'package:demo/src/features/settings/presentation/settings_page.dart'; import 'package:demo/src/features/wallpaper/presentation/wall_list.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; class PageViewWidget extends StatefulWidget { const PageViewWidget({super.key}); @override State<PageViewWidget> createState() => _PageViewWidgetState(); } class _PageViewWidgetState extends State<PageViewWidget> { final pageController = PageController(initialPage: 0); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Breezy'), ), body: Consumer( builder: (context, ref, child) { final pageviewService = ref.watch(pageviewServiceProvider.notifier); final pageviewServiceRead = ref.watch(pageviewServiceProvider); return Stack( children: [ PageView.builder( itemCount: 4, controller: pageController, onPageChanged: (int index) { pageviewService.onPageChanged(index); // pageviewService.changeShowBNB(true); }, itemBuilder: (context, index) { switch (index) { case 0: return const WallpaperList(); case 1: return const CategoriesPage(); case 2: return const FavouritesPage(); case 3: return const SettingsPage(); default: // Should never get hit. return CircularProgressIndicator( color: Theme.of(context).primaryColor, ); } }), Positioned.fill( bottom: 20, child: SafeArea( child: BlurBottomNav( onItemSelected: (int value) { pageviewService.onTapByBnb(value, pageController); }, selectedIndex: pageviewServiceRead.currentindex, items: navItems, ), ), ), ], ); }, )); } }
pageview_service.dart
import 'package:demo/src/features/pageview/domain/pageview_navigation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; class PageViewService extends Notifier<PageViewNavigation> { @override PageViewNavigation build() { return PageViewNavigation( currentindex: 0, changedByBNB: false, showBNB: true); } void changeShowBNB(bool value) { state.showBNB = value; } void changeIndex(int index) { state.currentindex = index; } void _changeBNBBool(bool value) { state.changedByBNB = value; } void onPageChanged(int index) { if ((index != state.currentindex) && (!state.changedByBNB)) { changeIndex(index); } else { _changeBNBBool(false); } } void onTapByBnb(int index, PageController pageController) { if (index != state.currentindex) { if (pageController.hasClients) { _changeBNBBool(true); changeIndex(index); pageController.animateToPage( index, curve: Curves.fastOutSlowIn, duration: const Duration(milliseconds: 350), ); } } } } final pageviewServiceProvider = NotifierProvider<PageViewService, PageViewNavigation>(PageViewService.new);
bottom_nav.dart
import 'dart:ui'; import 'package:demo/src/features/pageview/domain/nav_button.dart'; import 'package:demo/src/features/pageview/presentation/bottom_nav/nav_buttons.dart'; import 'package:flutter/material.dart'; class BlurBottomNav extends StatelessWidget { final List<BottomNavBarItem> items; final ValueChanged<int> onItemSelected; final int selectedIndex; const BlurBottomNav({ super.key, required this.items, required this.onItemSelected, required this.selectedIndex, }); @override Widget build(BuildContext context) { return Align( alignment: Alignment.bottomCenter, child: SizedBox( width: MediaQuery.of(context).size.width * 0.65, height: 57, child: ClipRRect( borderRadius: BorderRadius.circular(15), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), child: Container( height: 100, color: Colors.red, child: Padding( padding: const EdgeInsets.all(8.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: items.map((item) { var index = items.indexOf(item); return GestureDetector( onTap: () => onItemSelected(index), child: NavButton( item: item, isSelected: index == selectedIndex, ), ); }).toList(), ), ), ), ), ), ), ); } }
bottom_nav_button.dart
import 'dart:ui'; import 'package:demo/src/features/pageview/domain/nav_button.dart'; import 'package:flutter/material.dart'; class NavButton extends StatelessWidget { final BottomNavBarItem item; final bool isSelected; const NavButton({ super.key, required this.item, required this.isSelected, }); @override Widget build(BuildContext context) { return Stack( children: [ ClipRRect( borderRadius: BorderRadius.circular(15), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), child: AnimatedContainer( duration: const Duration(milliseconds: 200), height: 100, width: 43, decoration: BoxDecoration( borderRadius: BorderRadius.circular(15), border: Border.all( color: Colors.purple, width: 2, ), color: isSelected ? item.activeIconColor : Colors.white, ), child: item.icon, ), ), ), ], ); } }
BottomNavBarItem
import 'package:flutter/material.dart'; class BottomNavBarItem { BottomNavBarItem({ required this.icon, required this.activeIconColor, }); final Widget icon; final Color activeIconColor; }
问题根源与解决步骤
核心问题
Riverpod 2.x的Notifier遵循不可变状态设计原则:必须通过替换整个state对象来触发UI更新,直接修改state的属性不会通知监听者(因为Notifier内部通过对比state的引用变化来判断是否需要更新)。而ChangeNotifier是通过手动调用notifyListeners()来通知,所以直接修改属性可以工作,但Notifier不支持这种方式。
具体修改方案
将
PageViewNavigation改为不可变类
添加final字段和copyWith方法,方便创建新的状态对象:// pageview_navigation.dart class PageViewNavigation { final int currentindex; final bool changedByBNB; final bool showBNB; const PageViewNavigation({ required this.currentindex, required this.changedByBNB, required this.showBNB, }); // 复制当前状态并修改指定属性,返回新对象 PageViewNavigation copyWith({ int? currentindex, bool? changedByBNB, bool? showBNB, }) { return PageViewNavigation( currentindex: currentindex ?? this.currentindex, changedByBNB: changedByBNB ?? this.changedByBNB, showBNB: showBNB ?? this.showBNB, ); } }修改
PageViewService中的状态更新逻辑
不再直接修改state的属性,而是通过state = state.copyWith(...)替换整个状态对象:// pageview_service.dart class PageViewService extends Notifier<PageViewNavigation> { @override PageViewNavigation build() { return const PageViewNavigation( currentindex: 0, changedByBNB: false, showBNB: true, ); } void changeShowBNB(bool value) { state = state.copyWith(showBNB: value); } void changeIndex(int index) { state = state.copyWith(currentindex: index); } void _changeBNBBool(bool value) { state = state.copyWith(changedByBNB: value); } // 其余方法保持不变 }验证UI监听逻辑
PageViewWidget中的Consumer已经正确监听了pageviewServiceProvider,当state通过copyWith更新后,pageviewServiceRead.currentindex会同步变化,BlurBottomNav的selectedIndex参数更新,从而触发底部导航栏的UI重建。
额外说明
- 无需将
NavButton改为Consumer,因为BlurBottomNav已经通过selectedIndex接收了最新状态,参数变化会自动触发子组件重建。 - Riverpod的
Notifier设计强制不可变状态,能避免很多状态不一致的问题,是推荐的写法。
内容的提问来源于stack exchange,提问作者Dhanraj Priyadarshi

