LoginSignup
6
0

FlutterのRiverpodで配列を扱っているとref.watchで内容が更新されない時の対処法

Last updated at Posted at 2022-09-16

追記

StateProviderは現在非推奨になってます。StateNotifierProviderを使いましょう。

どのような場合に起こるか

RiverpodでListを使用しているときに内容を変更したのにそれがref.watchで監視している場所に通知されないことがあります。
そのような場合おそらくこのような書き方をしていませんか?

Dart
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はイミュータブルにしないといけないと書いてあるのでちゃんと読みましょう。

どうすれば良いのか

新しく配列を作り直し,それを代入すれば通知されます。
新しく配列を作る際はスプリット演算子などを用いて以下のようにする必要があります。

Dart
    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公式ドキュメント)

最後に

何かご指摘等ありましたらコメントにお願いします。

6
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
0