alpha Lounge

20%の技術記事とオタクネタ

【Flutter】Riverpodのプロバイダ修飾子「.autoDispose」と「.family」を理解する

Flutterで人気の状態管理ライブラリ「Riverpod」において、忘れてはいけないプロバイダ修飾子について、備忘録がてら書きます。

プロバイダ修飾子とは、プロバイダを作成する際に付ける修飾子のことで、執筆時点では.autoDispose.familyの2つが用意されています。
公式では以下の使用例があります。

final userProvider = FutureProvider.autoDispose.family<User, int>((ref, userId) async {
  return fetchUser(userId);
});

.autoDispose

「プロバイダの監視が終わったタイミングで、プロバイダに自動でステートを破棄させることができるようになります。」と公式で書かれています。

通常は作成されたプロバイダはどこからも参照されなくなったとしても破棄されずに残り、ステートも残り続けます。メモリリークの原因になったり、再利用する際に以前のステートが邪魔になってしまうことがあります。

.autoDisposeを使用すると、参照されなくなった(ref.watchしているWidgetが破棄されるなど)場合に破棄され、ステートがリセットされます。メモリリークを未然に防ぐことができ、以前のステートを気にする必要がなくなります。
基本的にWidgetの状態管理をするプロバイダは、Widgetが破棄された時に連動して初期化してほしいので、.autoDisposeを設定しておくとよさそうですね。

.autoDispose内部処理

この修飾子をつけるとProviderはAutoDisposeXXXProviderとして作成されます。例えば、StateNotifier用のAutoDisposeNotifierProviderは以下の通り。

class _AutoDisposeNotifierProvider<Notifier extends StateNotifier<State>, State>
    extends AutoDisposeProviderBase<Notifier> {
...
  @override
  Notifier create(
    covariant AutoDisposeStateNotifierProviderRef<Notifier, State> ref,
  ) {
    final notifier = _create(ref);
    ref.onDispose(notifier.dispose);
    return notifier;
  }
...
}

StateNotifier作成時に自動でdispose処理を登録してくれます。

.family

「プロバイダ外部の値を用いてプロバイダを作成できるようになります。」と公式で書かれています。

任意のデータをinjectしてプロバイダを作成することができます。

Widgetの状態管理としてプロバイダを使う時に、以下のように外部からデータを入れたい場面が結構ありますよね。

  • 詳細画面を表示する際に、fetch用のIDなどをプロバイダに渡したい
    • 公式の使用例でいうと、ユーザー詳細画面でfetch用のuserIdを渡す
  • ListViewやGridViewで表示する要素に、画像などの表示するデータを渡したい

こういった時にデータを渡すことができる修飾子です。

.family内部処理

この修飾子をつけるとProviderはXXXProviderFamilyとして作成されます。例えば、StateNotifier用のStateNotifierProviderFamilyは以下の通り。

class StateNotifierProviderFamily<Notifier extends StateNotifier<State>, State,
    Arg> extends Family<State, Arg, StateNotifierProvider<Notifier, State>> {
  /// {@macro riverpod.statenotifierprovider.family}
  StateNotifierProviderFamily(
    this._create, {
    String? name,
    List<ProviderOrFamily>? dependencies,
  }) : super(name: name, dependencies: dependencies);

  ...

  @override
  StateNotifierProvider<Notifier, State> create(
    Arg argument,
  ) {
    return StateNotifierProvider<Notifier, State>(
      (ref) => _create(ref, argument),
      name: name,
      from: this,
      argument: argument,
    );
  }
  ...
}

深く処理を追っていませんが、渡したデータ(argument)がcreate関数に渡されるところは確認できますね。

コードサンプル

2つのプロバイダ修飾子を使って、公式サンプルのようにユーザー情報を表示する画面を想定して、ViewModelをStateNotifierで作ります。

final userDetailViewModelProvider = StateNotifierProvider.autoDispose
    .family<UserDetailViewModel, AsyncValue<User>, int>(
        (ref, userId) {
  return UserDetailViewModel(userId);
});

class UserDetailViewModel extends StateNotifier<AsyncValue<User>> {
  final int userId;

  UserDetailViewModel(this.userId) : super(const AsyncValue.loading()) {
  // Userモデルのfetch処理とか書く
  }
}

宣言がやや複雑ですが、このようにViewModelを表現することができます。
ちなみにプロバイダ修飾子を2つ続けて使用すると、AutoDisposeXXXProviderFamilyとして作成されます。

参考

プロバイダ修飾子