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

使用Riverpod的Notifier时BlurBottomNav UI不更新问题求助

Flutter Riverpod Notifier 底部导航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不支持这种方式。

具体修改方案

  1. 将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,
        );
      }
    }
    
  2. 修改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);
      }
    
      // 其余方法保持不变
    }
    
  3. 验证UI监听逻辑
    PageViewWidget中的Consumer已经正确监听了pageviewServiceProvider,当state通过copyWith更新后,pageviewServiceRead.currentindex会同步变化,BlurBottomNav的selectedIndex参数更新,从而触发底部导航栏的UI重建。

额外说明

  • 无需将NavButton改为Consumer,因为BlurBottomNav已经通过selectedIndex接收了最新状态,参数变化会自动触发子组件重建。
  • Riverpod的Notifier设计强制不可变状态,能避免很多状态不一致的问题,是推荐的写法。

内容的提问来源于stack exchange,提问作者Dhanraj Priyadarshi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 23:35:18