【Flutter】Riverpod の値の表示と更新方法に対する質問への回答

Flutter Advent Calendar 2022」に参加させて頂きます!21日目です。

Riverpod の頂いた表示と更新の質問

UdemyにてRiverpodを使ったMVVMパターンの講座を公開しています。
そのなかに「Riverpodで表示と更新の方法がいくつか紹介されているけど、どれを使うのがいいんですか」というものがありました。質問を頂いたときは、「おそらく」という回答しかできませんでしたが、Riverpodのバージョンが2になってから、明確に返答ができるようなりました。
そのため自分なりの回答と理由を述べます。

回答

Riverpodを使った値の表示

ref.watch(_stateProvider)

Riverpodを使った値の更新

新しい値で更新するとき

ref.read(_stateProvider.notifier).state = 1

テスト用の全ソースの中では、プログラムの仕様上、前回の値を使ってます。
実際はロジックの計算結果やユーザからの入力値が設定されます。

現在の値を使って更新するとき

ref.read(_stateProvider.notifier).update((state) => state + 1);

前の値を知って、その値に処理をして、値を更新するときに使います。

候補

以前の候補と駄目な理由を挙げていきます

Riverpodを使った値の駄目な表示

*ref.watch(_stateProvider.state).state
_stateProvider.stateのstateが非推奨になっている
*ref.watch(_stateProvider.notifier).state
リアクティブに表示されない

Riverpodを使った値の駄目な更新

  • ref.read(_stateProvider.notifier).state = ref.read(_stateProvider.notifier).state + 1
    更新はできるが、ref.read(_stateProvider.notifier).stateが長い
  • ref.read(_stateProvider4.state).state = ref.read(_stateProvider4.state).state + 1;
    _stateProvider.stateのstateが非推奨になっている

まとめ

ということで、Riverpodの表示と更新方法を紹介しました。次に「どれを使えばいいんですか」と聞かれても、このページを回答すれば良くなりました。

テスト用の全ソース

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ProviderScope(
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: const MyHomePage(title: 'Flutter Demo Home Page'),
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final _stateProvider1 = StateProvider<int>((ref) => 0);
  final _stateProvider2 = StateProvider<int>((ref) => 0);
  final _stateProvider3 = StateProvider<int>((ref) => 0);
  final _stateProvider4 = StateProvider<int>((ref) => 0);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Consumer(
              builder: (context, ref, child) => Text(
                '${ref.watch(_stateProvider1)}',
              ),
            ),
            Consumer(
              builder: (context, ref, child) => Text(
                // 非推奨の表示
                '${ref.watch(_stateProvider2.state).state}',
              ),
            ),
            Consumer(
              builder: (context, ref, child) => Text(
                // リアクティブに更新されない
                '${ref.watch(_stateProvider3.notifier).state}',
              ),
            ),
            Consumer(
              builder: (context, ref, child) => Text(
                '${ref.watch(_stateProvider4)}',
              ),
            ),
          ],
        ),
      ),
      floatingActionButton: Consumer(
        builder: (context, ref, child) => FloatingActionButton(
          onPressed: () {
            // 両方OK (多分推奨)
            ref.read(_stateProvider1.notifier).state =
                ref.read(_stateProvider1) + 1;
            ref.read(_stateProvider2.notifier).update((state) => state + 1);

            // 両方OK
            ref.read(_stateProvider3.notifier).state =
                ref.read(_stateProvider3.notifier).state + 1;

            // 非推奨
            ref.read(_stateProvider4.state).state =
                ref.read(_stateProvider4.state).state + 1;

            ref.read(_stateProvider4.state).state =
                ref.read(_stateProvider4.state).state + 1;
          },
          tooltip: 'Increment',
          child: const Icon(Icons.add),
        ),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}