Closed2

【Riverpod】autoDisposeをしているProviderでref.readをしても大丈夫?

oke331oke331

時々、下記のようなrefが破棄されている旨のメッセージが出ませんか?

Cannot use "ref" after the widget was disposed.

上記が出ると「非同期処理でawaitしてる間に破棄されちゃったんだなぁ」くらいの認識でいたのですが、
下記のようなautoDisposeをしているProviderについて、
refはなんで使えるの?disposeされてないの?」と疑問に思い調べたのでメモ程度に記載します。

// autoDisposeなProvider

Repository repository(Ref ref) {
  return Repository(ref);
}

class Repository {
  Repository(this.ref);
  final Ref ref;

  Future<HogeFuga> fetch() async {
    final hoge = await ref.read(hogeProvider).fetch();
    
    // autoDisposeをしている、
    // かつ呼び出し元がref.read(repositoryProvider)をしている関係で、
    // この時点ですでにrepositoryProviderはonDisposeが呼ばれている。
    // 
    // このとき、repositoryProviderのrefは破棄されていると判断されないのか疑問に思った。
    final fuga = await ref.read(fugaProvider).fetch();
    
    return HogeFuga(hoge, fuga);
  }
}

// 呼び出し元
void hogeFunction(WidgetRef ref) async{
  final repository = ref.read(repositoryProvider);
  final result = await repository.fetch();
}
oke331oke331

結論

WidgetRefRefでは内部的にProviderContainerへのアクセス方法が違うので、
RefについてはProviderが破棄されていようとref.readを使用して問題ない。

調査したこと

WidgetRefRefについて、ref.readをしたときの処理の流れを見てみると、
下記の違いがあることがわかりました。
ProviderContainerとはプロバイダーの状態を保持する中心的なオブジェクトです。

  • WidgetRef: ProviderScope.containerOfで上位にあるProviderContainerに自分のBuildContextを使用して遡っているため、WidgetのBuildContextmountしているかをチェックしており、破棄されているとエラーが起きる可能性がある。
  • Ref: WidgetRefと同じくProviderContainerを使用しているが、BuildContextから読むのではなくRef(ProviderElementBase)にて保持しているProviderContainerを使用するので、特にエラーは起きることがない。

下記に簡易的ですが、処理の流れを記載します。

WidgetRefの場合

下記を呼び出す。

// refは[WidgetRef]
ref.read(hogeProvider);

⇒ ConsumerStatefulElement → read

  @override
  T read<T>(ProviderListenable<T> provider) {
    _assertNotDisposed();
    return ProviderScope.containerOf(this, listen: false).read(provider);
  }
  
  void _assertNotDisposed() {
	  // ここでcontextがmountされているかを確認している!
    if (!context.mounted) {
      throw StateError('Cannot use "ref" after the widget was disposed.');
    }
  }

⇒ ProviderContainer → read

  Result read<Result>(
    ProviderListenable<Result> provider,
  ) {
    return provider.read(this);
  }

⇒ ProviderBase → read

  @override
  StateT read(Node node) {
    final element = node.readProviderElement(this);
    return element.requireState;
  }

Refの場合

// refは[Ref]
ref.read(hogeProvider);

⇒ ProviderElementBase → read

  @override
  T read<T>(ProviderListenable<T> provider) {
    _assertNotOutdated();
    return _container.read(provider);
  }
  
  void _assertNotOutdated() {
	  // 特にmountされているかどうかはチェックしていない!
    assert(
      !_didChangeDependency,
      'Cannot use ref functions after the dependency of a provider changed but before the provider rebuilt',
    );
  }

⇒ ProviderContainer → read

  Result read<Result>(
    ProviderListenable<Result> provider,
  ) {
    return provider.read(this);
  }

⇒ ProviderBase → read

  @override
  StateT read(Node node) {
    final element = node.readProviderElement(this);
    return element.requireState;
  }
このスクラップは3ヶ月前にクローズされました