追記
StateProviderは現在非推奨になってます。StateNotifierProviderを使いましょう。
どのような場合に起こるか
RiverpodでListを使用しているときに内容を変更したのにそれがref.watchで監視している場所に通知されないことがあります。
そのような場合おそらくこのような書き方をしていませんか?
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
final textListProvider = StateProvider<List<String>>((ref) => []);
final numberProvider = StateProvider<int>((ref) => 0);
class ChatView extends ConsumerStatefulWidget {
const ChatView({Key? key}) : super(key: key);
@override
ChatViewState createState() => ChatViewState();
}
class ChatViewState extends ConsumerState<ChatView> {
void addText() {
ref.read(textListProvider.notifier).state.add(ref.read(numberProvider).toString());
ref.read(numberProvider.notifier).state++;
}
@override
Widget build(BuildContext context) {
final texts = ref.watch(textListProvider);
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: List<Widget>.generate(texts.length, (i) {
return Center(
child: Text(
texts[i],
),
);
}),
),
floatingActionButton: FloatingActionButton(
onPressed: addText,
),
);
}
}
ここで注目してほしいのがaddTextメソッドで,ref.readで取得してきたtextListProviderの値をaddを用いて変更しています。このような場合は変更が通知されず,ref.watchで監視していたとしてもWidgetは更新されません。
なぜか
これはStateがイミュータブル(変化しないもの)でなければならないことに起因します。
addやinsertなどの関数を利用して値を変更してもそのProviderで使用されている変数のインスタンスは変化していないので変更が通知されず,結果的にWidgetも更新されないという仕組みです。
これに関して特にコンパイルエラーやLinterのエラーも発生せず,一度HotReloadを挟むと変更された値が表示されてしまいます。そのため非常にエラーの原因に気づきにくいものになっています。
ドキュメントにはStateはイミュータブルにしないといけないと書いてあるのでちゃんと読みましょう。
どうすれば良いのか
新しく配列を作り直し,それを代入すれば通知されます。
新しく配列を作る際はスプリット演算子などを用いて以下のようにする必要があります。
final t = ref.read(textListProvider);
t.add(ref.read(numberProvider).toString());
ref.read(textListProvider.notifier).state = [...t];
ここで注意しなければいけないのは直接tを代入してはいけないということです。直接tを代入してしまうとデータのメモリアドレスが変更されないため通知されないのだと考えられます。スプリット演算子を用いた[...t]
の書き方であれば完全に新しい配列になるので通知されます。
参考文献
【Riverpod】Listの値を更新したのにref.watchで再描画されない
StateNotifierProvider(Riverpod公式ドキュメント)
最後に
何かご指摘等ありましたらコメントにお願いします。