LoginSignup
5

More than 1 year has passed since last update.

モチベーション

皆様、Flutter Riverpodの.familyについてただ単にProviderを利用する際に引数を与えられるだけと思っていませんでしたか(私もつい最近までそう思っていました)。それにしても何故familyと名前がついているのか?今まで疑問でしたが、最近解消しましたので記載します。

.familyについて

Riverpodの.family正式ドキュメントに使い方や解説がありますが、使用時に引数を指定することが可能にします。
例としては以下のように.familyを使用したProviderを定義して。(引用元

final messagesFamily = FutureProvider.family<Message, String>((ref, id) async {
  return dio.get('http://my_api.dev/messages/$id');
});

使用する際に以下のように引数にidを与えることができます。通常の関数のように使用時に引数を指定することが可能です。

Widget build(BuildContext context, WidgetRef ref) {
  final response = ref.watch(messagesFamily('id'));
}

このまま関数のように使うのは便利ですが、一意の値を取得することもできます。

.familyで一意の値を取得する

正式ドキュメントの冒頭に書いてありますが、用途として引数が識別するIDとなり一意のプロバイダを取得することが可能です。そのことをサンプルコードを用いて紹介します

一意のプロバイダーを取得する例

サンプルコード全体

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とt3の比較ではfalseを返す
  print(t1 == t3); // false

}

コードの詳細

.familyの定義

以下のtargetProviderFamilyは引数に関係なくTarget()のインスタンスを返します。ここで注意はこの例では引数に関係ないインスタンスを返している事です

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

次にmainの中で同じ引数=0をプロバイダに指定した値を取り出して==にて比較します。この時の結果はtrueになります。これは引数が同じ場合は同じTarge()のインスタンスを返すことを意味します。

  // 同じ引数=0を指定
  final t1 = container.read(targetProviderFamily(0));
  final t2 = container.read(targetProviderFamily(0));
  // 同じインスタンスのためtrueにになる
  print(t1 == t2); // true

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

.familyの引数が異なる値の場合

上記に追加で以下の部分では異なる引数=1を指定した値と前回の引数=0で所得した値を比較するとfalseになります。

  // 異なる引数=1を指定
  final t3 = container.read(targetProviderFamily(1));
  // t1とt3の比較ではfalseを返す
  print(t1 == t3); // false

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

サンプルコードから

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

引数の注意点

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

hashCode==OperatorをOverride例

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を使うともっと簡単に書けます。

最後に

Riverpodの.familyには何故familyと名前がついているのか?なんとなくなく理解できたかと思います。
よく使うProviderには.familyを利用して再利用できるようにすればアプリのリソース効率につながります。
.familyの弱点としては、引数の数は1個の制限があります。しかし複数値を利用した場合の解決策としては正式ドキュメントの中で先ほどのequatabletupleを使うことで可能です(私は引数にClass定義するのは面倒なのでtupleをよく利用しています)。

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
5