I am using Riverpod and have set up a Provider and Notifier for my data, however when I modify the data in the provider, my UI is not reflecting that change. If I save and hot-reload my app, the new values are shown.
For context, my Board contains a property that is a Map of Players and their Scores. On my Board screen, I am iterating through a list conversion of the 'boardData' map for a GridView.builder, like so:
ref.read(boardProvider.notifier).loadBoard(widget.board);
final providedBoard = ref.watch(boardProvider);
List<MapEntry<PlayerModel, int>> boardDataList =
providedBoard.boardData.entries.toList();
body: GridView.builder(
padding: const EdgeInsets.all(16),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 3 / 2, //1.5
crossAxisSpacing: 10,
mainAxisSpacing: 10,
),
itemCount: providedBoard.boardData.length,
itemBuilder: (context, index) => BoardPlayerGridItem(
player: boardDataList[index].key,
score: boardDataList[index].value,
),
),
Here is my BoardModel class:
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; // default to 0
final int increment; // default to 1
final Map<PlayerModel, int> boardData; // Players and scores for the board
final PlayerModel owner;
final bool isManaged; // Only owner can make changes
final bool isOnline; // Saved to cloud
final bool autosaveEnabled;
final DateTime lastUpdated;
}
Here is my provider:
class BoardNotifier extends StateNotifier<BoardModel> {
BoardNotifier() : super(sampleBoard);
// Load the currently viewed board into notifier
loadBoard(BoardModel board) {
state = board;
}
// Update the player's score in the board by the configured increment
incrementPlayerScore(BoardModel board, PlayerModel player) {
board.boardData.update(player, (value) => value + board.increment);
// Update our board in state
state = board;
}
}
final boardProvider = StateNotifierProvider<BoardNotifier, BoardModel>((ref) {
return BoardNotifier();
});
Here is my BoardPlayerGridItem (the widget in the GridView) that is not updating.
class BoardPlayerGridItem extends ConsumerStatefulWidget {
const BoardPlayerGridItem({
super.key,
required this.player,
required this.score,
});
final PlayerModel player;
final int? score;
@override
ConsumerState<ConsumerStatefulWidget> createState() =>
_BoardPlayerGridItemState();
}
class _BoardPlayerGridItemState extends ConsumerState<BoardPlayerGridItem> {
@override
Widget build(BuildContext context) {
final providedBoard = ref.watch(boardProvider);
return GestureDetector(
onTap: () {
ref.watch(boardProvider.notifier).incrementPlayerScore(
providedBoard,
widget.player,
);
},
onLongPress: () {}, // Open edit form
child: Container(
color: Theme.of(context).highlightColor,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
widget.player.name,
style: Theme.of(context)
.textTheme
.titleMedium!
.copyWith(color: Theme.of(context).colorScheme.onBackground),
),
const SizedBox(height: 10),
Text(
widget.score.toString(),
style: Theme.of(context)
.textTheme
.displayLarge!
.copyWith(color: Theme.of(context).colorScheme.onBackground),
),
],
),
),
);
}
}
The UI is a grid of tiles and tapping on each increments the counter. I thought by adding watching my boardProvider and then changing it as per the onTap function, that would prompt the rebuild, but seems not. Any pointers appciated.
The problem is that inside incrementPlayerScore
, you're already updating the board before reassigning the state
. This is why it's important to use immutable state.
As a workaround, you can make a local variable inside incrementPlayerScore
that copy the board
state first and make modifications on that copied variable. You haven't shown the BoardModel
class, but I assume it should have some kind of copyWith
method. If so, you can do this:
incrementPlayerScore(BoardModel board, PlayerModel player) {
final newBoard = board.copyWith();
newBoard.boardData.update(player, (value) => value + board.increment);
// Update our board in state
state = newBoard;
}
(Note: there could be a better way to use the copyWith
that is by assigning the new boardData
with the boardData
parameter of copyWith
)
Additionally, you should use ref.read
instead of ref.watch
inside the onTap
of GestureDetector
.