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

Riverpod修改Provider数据后UI未更新问题求助

Riverpod修改数据后UI不更新的问题修复

你遇到的问题是:使用Riverpod的StateNotifier管理BoardModel状态时,点击网格项修改玩家分数后,UI不会自动刷新,只有热重载后才能看到新分数。

问题根源

  1. 状态未真正更新:BoardModel中的boardData是可变Map,你直接修改了这个Map的值,然后把同一个BoardModel实例重新赋值给state。Riverpod通过对比状态实例的引用来判断是否需要通知监听者,引用未改变时,不会触发UI重建。
  2. 子组件依赖旧值:BoardPlayerGridItem组件依赖初始化时传入的widget.score显示分数,即使父组件重建,子组件也不会主动获取最新状态中的分数。

修复方案

1. 让BoardModel成为不可变类并实现copyWith方法

修改BoardModel,添加copyWith方法确保每次修改都创建新实例,而非修改原有属性:

import 'package:flutter/material.dart';
import 'package:uuid/uuid.dart';
import 'package:counter/models/player_model.dart';

const uuid = Uuid();

class BoardModel {
  BoardModel({
    required this.name,
    required this.icon,
    required this.initialValue,
    required this.increment,
    required this.boardData,
    required this.owner,
    required this.isManaged,
    required this.isOnline,
    required this.autosaveEnabled,
    required this.lastUpdated,
    String? id,
  }) : id = id ?? uuid.v4();

  final String id;
  final String name;
  final Icon icon;
  final int initialValue;
  final int increment;
  final Map<PlayerModel, int> boardData;
  final PlayerModel owner;
  final bool isManaged;
  final bool isOnline;
  final bool autosaveEnabled;
  final DateTime lastUpdated;

  // 用于创建新实例的copyWith方法
  BoardModel copyWith({
    String? name,
    Icon? icon,
    int? initialValue,
    int? increment,
    Map<PlayerModel, int>? boardData,
    PlayerModel? owner,
    bool? isManaged,
    bool? isOnline,
    bool? autosaveEnabled,
    DateTime? lastUpdated,
    String? id,
  }) {
    return BoardModel(
      name: name ?? this.name,
      icon: icon ?? this.icon,
      initialValue: initialValue ?? this.initialValue,
      increment: increment ?? this.increment,
      boardData: boardData ?? this.boardData,
      owner: owner ?? this.owner,
      isManaged: isManaged ?? this.isManaged,
      isOnline: isOnline ?? this.isOnline,
      autosaveEnabled: autosaveEnabled ?? this.autosaveEnabled,
      lastUpdated: lastUpdated ?? DateTime.now(),
      id: id ?? this.id,
    );
  }
}

2. 修改BoardNotifier,创建新状态实例

更新incrementPlayerScore方法,不再直接修改原Map,而是创建新的Map和BoardModel实例:

class BoardNotifier extends StateNotifier<BoardModel> {
  BoardNotifier() : super(sampleBoard);

  void loadBoard(BoardModel board) {
    state = board;
  }

  void incrementPlayerScore(PlayerModel player) {
    // 创建新Map并复制原有数据,更新指定玩家分数
    final newBoardData = Map<PlayerModel, int>.from(state.boardData);
    newBoardData.update(
      player,
      (value) => value + state.increment,
      ifAbsent: () => state.initialValue,
    );

    // 使用copyWith创建新实例并更新状态
    state = state.copyWith(
      boardData: newBoardData,
      lastUpdated: DateTime.now(),
    );
  }
}

final boardProvider = StateNotifierProvider<BoardNotifier, BoardModel>((ref) {
  return BoardNotifier();
});

3. 修复BoardPlayerGridItem,读取最新状态

将子组件改为ConsumerWidget,直接从当前状态中获取最新分数,不再依赖传入的旧值:

class BoardPlayerGridItem extends ConsumerWidget {
  const BoardPlayerGridItem({
    super.key,
    required this.player,
  });

  final PlayerModel player;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final providedBoard = ref.watch(boardProvider);
    // 从当前状态获取最新分数
    final score = providedBoard.boardData[player] ?? providedBoard.initialValue;

    return GestureDetector(
      onTap: () {
        ref.read(boardProvider.notifier).incrementPlayerScore(player);
      },
      onLongPress: () {},
      child: Container(
        color: Theme.of(context).highlightColor,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              player.name,
              style: Theme.of(context)
                  .textTheme
                  .titleMedium!
                  .copyWith(color: Theme.of(context).colorScheme.onBackground),
            ),
            const SizedBox(height: 10),
            Text(
              score.toString(),
              style: Theme.of(context)
                  .textTheme
                  .displayLarge!
                  .copyWith(color: Theme.of(context).colorScheme.onBackground),
            ),
          ],
        ),
      ),
    );
  }
}

4. 优化父组件GridView构建

移除冗余的boardDataList转换,直接在itemBuilder中获取数据:

// 父组件body部分
body: GridView.builder(
  padding: const EdgeInsets.all(16),
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
    childAspectRatio: 3 / 2,
    crossAxisSpacing: 10,
    mainAxisSpacing: 10,
  ),
  itemCount: providedBoard.boardData.length,
  itemBuilder: (context, index) {
    final entry = providedBoard.boardData.entries.elementAt(index);
    return BoardPlayerGridItem(
      player: entry.key,
    );
  },
),

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 21:45:35