Open7

「Riverpod」を勉強するスレ

Ryo24Ryo24

データの受け渡しの場合、

  • データを渡す側
  • データを受け取る側

が必要。
Riverpodでは、データを渡す側「Provider」(グローバル変数)で行う。

Provider (データを渡す側)の種類

  • Provider
    • 任意のデータを渡す / 他Providerに依存したProviderを作成する時に使う
  • FutureProvider
    • Futureから取得できる任意のデータを渡す
  • StreamProvider
    • Streamから取得できる任意のデータを渡す
  • StateProvide
    • 更新可能な任意のデータを渡す
  • StateNotifierProvider
    • StateNotifierから取得できる任意のデータを渡す
  • ScopedProvider
    • 場所に応じて、異なる任意のデータを渡す

データを受け取る側

  • ConsumerWidget
    • 継承することで、データを受け取れるWidget
  • Consumer
    • コールバック内でデータを受け取れるWidget
  • useProvider()
    • flutter_hooksを使い、HookWidgetを継承したWidgetでデータを受け取れる関数
  • context.read()
    • BuildContextからデータを受け取る関数(データの更新通知は不可)
  • ProviderContainer
    • Flutterに依存せず、データを受け取れる
Ryo24Ryo24

StateNotifierとは?

ドキュメントを読む限りだとクラスに1つだけ変数を作り、状態を管理する方法なのかな?

  • 以前の状態と新しい状態を比較する
  • 元に戻る-やり直しメカニズムを実装する
  • アプリケーションの状態をデバッグする

詳しく理解していないから、ちゃんと調べて理解する。

プログラムサンプル

class Counter extends StateNotifier<int> {
  Counter(): super(0);  // 初期値をコンストラクタに渡す

  void increment() => state++;
  void decrement() => state--;
}

https://pub.dev/documentation/state_notifier/latest/state_notifier/StateNotifier-class.html
https://itome.team/blog/2020/05/flutter-state-notifier-provider/

Ryo24Ryo24

Provider

Providerは状態の一部をカプセル化し、その状態をリッスンできるようにしたオブジェクトである。

なぜ使用するのか?

状態の一部をラップすることにより、

  • 複数の場所でアクセス可能
    • Singletons, Service Locators, Dependency Injection or InheritedWidgetの上位互換
  • 状態を組み合わせることが容易
    • プロパイダー内に単純な構文で直接構築可能
  • パフォーマンスの最適化
    • 状態の変化に影響される部分だけ、再計算されることを保証する
  • テストがしやすい
    • 複雑なsetUp/tearDownは必要ない
    • どのProviderもオーライドし、テスト中に異なる動作が可能

Providerを作成する

最も一般的な使用方法は、グローバル定数として宣言する

スタンダードな使用法
final myProvider = Provider((ref) {
  return MyValue();
});

これは3つのコンポーネントで構成している

  • ffinal myProvider変数
    • Providerの状態を読み取るために使用し、常に不変である必要がある。
  • Provider
    • 変更できないオブジェクトを提供する
  • 共有状態を作成する関数
  • 関数は常にrefパラメータとして呼び出し、オブジェクトを受け取る
  • 状態が吐きされたとき、他Providerの読み取りや操作を実行可能
Ryo24Ryo24
StateProvider
/// Providers are declared globally and specifies how to create a state
final counterProvider = StateProvider((ref) => 0);
read()
print(ref.read(counterProvider).runtimeType);  // int
print(ref.read(counterProvider));  // 18

print(ref.read(counterProvider.notifier).runtimeType);  // StateController<int>
print(ref.read(counterProvider.notifier));  // Instance of 'StateController<int>'

print(ref.read(counterProvider.state).runtimeType);  // StateController<int>
print(ref.read(counterProvider.state));  // Instance of 'StateController<int>'

print(ref.read(counterProvider.state).state.runtimeType);  // int
print(ref.read(counterProvider.state).state);  // 18

print(ref.read(counterProvider.notifier).state.runtimeType);  // int
print(ref.read(counterProvider.notifier).state);  // 18

.read

ref.readメソッドは、プロバイダの状態を余分な影響を与えずに取得する方法。
状態をリッスンしないため、値が更新されても検知できない。

ref.readの使用はできるだけ避ける。これは、watchlistenが使いにくい時の回避策として存在する。そのため、基本的にはwatchもしくはlistenを使う。ベストはwatchだよ!!!
引用元 : Using ref.read to obtain the state of a provider once​ Note

Ryo24Ryo24

Counterをコードリーディング

https://github.com/rrousselGit/river_pod/blob/master/examples/counter/lib/main.dart

ConsumerWidget

https://pub.dev/documentation/flutter_riverpod/latest/flutter_riverpod/ConsumerWidget-class.html
Providerが更新時、HomeのUIが全て再描画される。
⚠️build内で状態の変更やhttpリクエストをしない

Consumer

https://pub.dev/documentation/flutter_riverpod/latest/flutter_riverpod/Consumer-class.html
Providerが更新時、Consumerでwrapされた箇所が再描画される。

今回の最適解

ConsumerWidgetの最適解
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

// A Counter example implemented with riverpod

void main() {
  runApp(
    // Adding ProviderScope enables Riverpod for the entire project
    const ProviderScope(child: MyApp()),
  );
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: Home());
  }
}

/// Providers are declared globally and specifies how to create a state
final counterProvider = StateProvider((ref) => 0);

class Home extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(counterProvider.state).state;
    return Scaffold(
      appBar: AppBar(title: const Text('Counter example')),
      body: Center(
        child: Text('$count'),
      ),
      floatingActionButton: FloatingActionButton(
        // The read method is an utility to read a provider without listening to it
        onPressed: () => ref.watch(counterProvider.notifier).state++,
        child: const Icon(Icons.add),
      ),
    );
  }
}

ConsumerWidgetでラップしているから、このサンプルだとConsumerは必要ないと思う。