🤢

[Riverpod] FutureProviderやAsyncNotifierProviderでExceptionした時にログを表示する

2024/02/08に公開

Riverpodで開発をしていると FutureProviderAsyncNotifierProvider は日常的に使うかと思います。
それぞれが初期化中にエラーを発生させるとAsyncErrorとなり、ref.watch(provider).when(data: ...)でエラー時のUIを構築できます。

しかし、エラーログがコンソールに表示されないので、開発中に困ることが多々ありました。
そのため、アプリ全体でFutureProviderやAsyncNotifierProvider`の初期化中のエラーをログとして表示するようにしました。

riverpodが2.4.6以上

後述しますが、バージョンが2.4.5以下の場合、この方法では上手く行きません。
2.4.6以上の場合に使える方法です。

① ProviderObserverのproviderDidFailをoverrideしたクラスを作る

class _AppObserver extends ProviderObserver {
  
  void providerDidFail(
    ProviderBase<Object?> provider,
    Object error,
    StackTrace stackTrace,
    ProviderContainer container,
  ) {
    // ここでログを表示する。
    final logger = Logger();
    logger.e(
      error,
      error: error,
      stackTrace: stackTrace,
    );
  }
}

② ProviderScopeのobserversに指定する

main.dartとかで ProviderScopeを使っていると思いますが、ovservers_AppObserverを追加します。

runApp(
  ProviderScope(
    observers: [_AppObserver()], // ここに追加
      child: AppMaterialApp(
        home: const TextFieldScreen(),
      ),
    ),
  );

riverpodが2.4.5以下

個人で開発しているプロダクトのバージョンが最初2.4.4だったため、上記の方法で上手く行きませんでした。
原因としては、providerDidFailがトリガーされないためログが表示されていなかったのですが、
公式のPull requestsを見ると、こちらで改善されているみたいで2.4.6のバージョンに含まれているみたいです。

https://github.com/rrousselGit/riverpod/pull/3067

もちろんバージョンを上げれば解決するので、それでもいいですがバージョンを上げずにログを表示する方法も書いておきます。

① didUpdateProvideを使う

2.4.5以下だとproviderDidFailがトリガーされないため、代わりにdidUpdateProviderを使います。
ただ、そのまま使ってしまうとProvider更新時全てでログが流れてしまうため newValue is AsyncErrorの場合だけログを表示します。

class _AppObserver extends ProviderObserver {
  
  void didUpdateProvider(
    ProviderBase<Object?> provider,
    Object? previousValue,
    Object? newValue,
    ProviderContainer container,
  ) {
    if (newValue is AsyncError) {
      final logger = Logger();
      logger.e(
        newValue.error.toString(),
        error: newValue.error,
        stackTrace: newValue.stackTrace,
      );
    }
  }
}

② ProviderScopeのobserversに指定する

あとはは2.4.6以上と同じでobserversに指定してあげます。

runApp(
  ProviderScope(
    observers: [_AppObserver()], // ここに追加
      child: AppMaterialApp(
        home: const TextFieldScreen(),
      ),
    ),
  );

参考

https://riverpod.dev/ja/docs/essentials/provider_observer

https://github.com/rrousselGit/riverpod/pull/3067

Discussion