Flutter Riverpodの.familyについて

## モチベーション
皆様、Flutter Riverpodの`.family`についてただ単にProviderを利用する際に**引数を与えられるだけ**と思っていませんでしたか（私もつい最近までそう思っていました）。それにしても何故`family`と名前がついているのか？今まで疑問でしたが、最近解消しましたので記載します。

## .familyについて
Riverpodの`.family`は[正式ドキュメント](https://docs-v2.riverpod.dev/docs/concepts/modifiers/family)に使い方や解説がありますが、使用時に引数を指定することが可能にします。
例としては以下のように`.family`を使用したProviderを定義して。（[引用元](https://docs-v2.riverpod.dev/docs/concepts/modifiers/family#usage)）


```dart
final messagesFamily = FutureProvider.family<Message, String>((ref, id) async {
  return dio.get('http://my_api.dev/messages/$id');
});
```
使用する際に以下のように引数に`id`を与えることができます。通常の関数のように使用時に引数を指定することが可能です。
```dart
Widget build(BuildContext context, WidgetRef ref) {
  final response = ref.watch(messagesFamily('id'));
}
```
このまま関数のように使うのは便利ですが、一意の値を取得することもできます。

## .familyで一意の値を取得する
[正式ドキュメント](https://docs-v2.riverpod.dev/docs/concepts/modifiers/family)の冒頭に書いてありますが、用途として引数が識別するIDとなり一意のプロバイダを取得することが可能です。そのことをサンプルコードを用いて紹介します

### 一意のプロバイダーを取得する例
サンプルコード全体
```dart
import 'package:riverpod/riverpod.dart';
// Providerで返すclassを定義
class Target {
}

// Provider.familyを定義。引数に関係なく同じ値のインスタンスを返す
final targetProviderFamily = Provider.family<Target,int> ((ref, arg){
  return Target(); // 引数に関係なく同じ値のインスタンスを返す
});

void main() {
  final container = ProviderContainer();
  
  // 同じ引数=0を指定
  final t1 = container.read(targetProviderFamily(0));
  final t2 = container.read(targetProviderFamily(0));
  // 同じインスタンスのためtrueにになる
  print(t1 == t2); // true
  
  // 異なる引数=1を指定
  final t3 = container.read(targetProviderFamily(1));
  // t1とt３の比較ではfalseを返す
  print(t1 == t3); // false

}
```
#### コードの詳細
##### .familyの定義
以下の`targetProviderFamily`は引数に関係なく`Target()`のインスタンスを返します。ここで注意はこの例では**引数に関係ないインスタンスを返している**事です

```dart
// Provider.familyを定義。引数に関係なく同じ値のインスタンスを返す
final targetProviderFamily = Provider.family<Target,int> ((ref, arg){
  return Target(); // 引数に関係ないインスタンスを返す
});
```
##### .familyの引数が同じ値の場合
次に`main`の中で同じ`引数=0`をプロバイダに指定した値を取り出して`==`にて比較します。この時の結果は`true`になります。これは引数が同じ場合は同じ`Targe（）`のインスタンスを返すことを意味します。
```dart
  // 同じ引数=0を指定
  final t1 = container.read(targetProviderFamily(0));
  final t2 = container.read(targetProviderFamily(0));
  // 同じインスタンスのためtrueにになる
  print(t1 == t2); // true

```
詳しく言うと、`Target() == Target()`は異なるインスタンスとなるため`false`になるはずですが、`true`になるのは、**同じ引数では再度インスタンスを作成することなく一意のインスタンスを再利用している**ことを意味します。

##### .familyの引数が異なる値の場合
上記に追加で以下の部分では異なる`引数=1`を指定した値と前回の`引数=０`で所得した値を比較すると`false`になります。
```dart
  // 異なる引数=1を指定
  final t3 = container.read(targetProviderFamily(1));
  // t1とt３の比較ではfalseを返す
  print(t1 == t3); // false

```
**異なる引数では異なるインスタンスになる**ことを意味します。

#### サンプルコードから
`.family`は引数を**苗字代わりに一意の値を取得することが可能です**。この特性を利用して無駄にインスタンスを立てることなく再利用することが可能になります。

## 引数の注意点
引数に`int`や`String`のようにリテラルの値を使う場合は一意の値を返しますが、引数にObjectを使う場合は注意が必要です。Objectが同じ引数と確認するためにはObjectの`hashCode`と`==`OperatorをOverrideする必要があります。

### `hashCode`と`==`OperatorをOverride例
```dart
import 'package:riverpod/riverpod.dart';

// 引数となるClass
class Arg {
  final String value;
  const Arg(this.value);
  
  // override == operator
  @override
  bool operator == (Object other) {
    if (identical(this, other)) {
      return true;
    }
    if (other is Arg) {
      return runtimeType == other.runtimeType && value == other.value;
    } else {
      return false;
    }
  }

  // override hasCode
  @override
  int get hashCode => value.hashCode;
}

class Target {}

final targetProviderFamily = Provider.family<Target,Arg> ((ref, arg){
  return Target();
});

void main() {
  final container = ProviderContainer();
  
  // 同じ引数=0を指定
  final t1 = container.read(targetProviderFamily(const Arg('Zero')));
  final t2 = container.read(targetProviderFamily(const Arg('Zero')));
  // 同じインスタンス返す
  print(t1 == t2); // true
  
  // 異なる引数=1を指定
  final t3 = container.read(targetProviderFamily(const Arg('One')));
  // 異なるインスタンスになる
  print(t1 == t3); // false

}

```
引数となるObjectおClassで`hashCode`と`==`OperatorをOverrideすればリテラルの時と同様に使うことができます。
しかし`hashCode`と`==`OperatorをOverrideするのは面倒なので[equatable](https://pub.dev/packages/equatable)を使うともっと簡単に書けます。

## 最後に
Riverpodの`.family`には何故`family`と名前がついているのか？なんとなくなく理解できたかと思います。
よく使うProviderには`.family`を利用して再利用できるようにすればアプリのリソース効率につながります。
`.family`の弱点としては、**引数の数は１個の制限**があります。しかし複数値を利用した場合の解決策としては[正式ドキュメント](https://docs-v2.riverpod.dev/docs/concepts/modifiers/family#passing-multiple-parameters-to-a-family)の中で先ほどの[equatable](https://pub.dev/packages/equatable)や[tuple](https://pub.dev/packages/tuple)を使うことで可能です（私は引数にClass定義するのは面倒なので[tuple](https://pub.dev/packages/tuple)をよく利用しています）。