🧚‍♂️

【Riverpod学習】同じ型のProviderを複数提供できることについて

2024/10/10に公開

Riverpodでは、同じ型のProviderを複数提供する場合でも、それぞれのProviderがユニークな「キー」(プロバイダ自体)として扱われるため、簡単に区別できる仕組みがあります。これにより、FlutterのProviderパッケージと異なり、同じ型の複数の状態を同時に扱うことが可能です。

具体的な仕組み:Provider自体がキーになる

Riverpodでは、各Providerがプロバイダ変数そのもので管理されるため、同じ型の状態でも異なるプロバイダ変数を使って提供すれば、区別して扱うことができます。このため、同じ型の状態を複数提供しても、どれがどのProviderに紐づくかが明確に管理されるのです。

同じ型のProviderを複数使うケース

たとえば、2つのCounterモデルをそれぞれ別々に管理したい場合を考えます。Riverpodでは、次のようにそれぞれのCounter状態を異なるProvider変数に定義することで区別が可能です。

  1. Counterクラスを定義
class Counter {
  int value;
  Counter(this.value);
}
  1. RiverpodのProviderを定義
import 'package:flutter_riverpod/flutter_riverpod.dart';

// 1つ目のCounterを管理するProvider
final counterProvider1 = StateProvider((ref) => Counter(0));

// 2つ目のCounterを管理するProvider
final counterProvider2 = StateProvider((ref) => Counter(0));

counterProvider1 と counterProvider2 は同じ型(Counter)ですが、異なる変数名がそれぞれ異なるProviderを区別するための「キー」として機能します。
Riverpodでは、これらのProviderを区別して使うことができ、それぞれ独立した状態を保持します。
3. それぞれのProviderを使ってウィジェットに状態を表示する


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

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return ProviderScope(
      child: MaterialApp(
        home: Scaffold(
          appBar: AppBar(title: Text('Multiple Counters with Riverpod')),
          body: Column(
            children: [
              // 1つ目のCounterの状態を表示
              Consumer(
                builder: (context, ref, child) {
                  final counter1 = ref.watch(counterProvider1).value;
                  return Text('Counter 1: $counter1');
                },
              ),
              // 2つ目のCounterの状態を表示
              Consumer(
                builder: (context, ref, child) {
                  final counter2 = ref.watch(counterProvider2).value;
                  return Text('Counter 2: $counter2');
                },
              ),
              ElevatedButton(
                onPressed: () {
                  // 1つ目のCounterをインクリメント
                  context.read(counterProvider1).state.value++;
                },
                child: Text('Increment Counter 1'),
              ),
              ElevatedButton(
                onPressed: () {
                  // 2つ目のCounterをインクリメント
                  context.read(counterProvider2).state.value++;
                },
                child: Text('Increment Counter 2'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

解説

counterProvider1 と counterProvider2 はどちらも同じ型(Counter)を提供していますが、変数が異なるため、それぞれ独立して動作します。
ref.watch(counterProvider1) と ref.watch(counterProvider2) を使うことで、どちらの状態を参照するかが明確です。
ボタンを押すと、それぞれのCounterの値が個別にインクリメントされ、他方には影響を与えません。

まとめ

同じ型の複数Providerを簡単に区別できる: Providerごとに別の変数名で定義するため、同じ型でも問題なく同時に扱えます。
依存関係の明確化: それぞれのProviderがどの部分の状態を管理しているかが明確で、意図しない動作を防げます。
簡単に状態管理が可能: 複雑なウィジェットツリーを考慮せずに、同じ型の状態を異なる箇所で独立して管理できるため、コードがシンプルになります。
これにより、複数の同じ型の状態を扱いたい場合、RiverpodはProviderよりも使いやすく、柔軟に状態管理ができるツールと言えます。

Discussion