riverpod_annotationについてちょっと詳しくなるための記事

[YUMEMI Flutter Advent Calendar 2023](https://qiita.com/advent-calendar/2023/yumemi-flutter) 21日目の記事です。

## はじめに
はじめまして、ノリでアドカレ5本書くことにしたもののネタが無いK9iです。
今回はRiverpodを使う際にdependenciesに追加しているものの、なんなのかよく分かってない人もいるかもしれないriverpod_annotationについて記事にしてみました。

## 本編

### riverpod_annotation
riverpod_annotationは名前の通り[riverpod_generator](https://pub.dev/packages/riverpod_generator)と組み合わせて使うannotation(注釈)のパッケージです。

https://pub.dev/packages/riverpod_annotation

## 使い方
riverpod_generatorを使うときにpubspec.yamlに記述します。


```yaml:pubspec.yaml
dependencies:
　　　　flutter_riverpod:
  riverpod_annotation:

dev_dependencies:
  build_runner:
  riverpod_generator:
```

riverpod_generatorがdev_dependenciesなのに対して、riverpod_annotationはdependenciesに記述します。
同様の例としてfreezedとfreezed_annotation、json_serializableとjson_annotationがありますね。

### annotationとは
ChatGPTの回答
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2542067/6d118965-c947-a099-47b9-ce0d9da8a854.png)

コードにメタデータを追加する構文です。

### 具体例
riverpod_annotationには記事執筆時点で3つのannotationがあります。
順番に見ていきます。
ソースはここにあります。

https://github.com/rrousselGit/riverpod/blob/master/packages/riverpod_annotation/lib/src/riverpod_annotation.dart

#### Riverpodアノテーション
riverpod_generatorでProviderの元となる関数、もしくはクラスに@riverpodのように指定するアレです。
keepAliveもしくはdependenciesを引数に取ります。引数があるときは大文字始まりで@Riverpod、無いときは@riverpodとします。@riverpodは実装的にはconst riverpod = Riverpod();となっていることが分かります👀

```dart:riverpod_annotation.dart
@Target({TargetKind.classType, TargetKind.function})
@sealed
class Riverpod {
  const Riverpod({
    this.keepAlive = false,
    this.dependencies,
  });

~~~

@Target({TargetKind.classType, TargetKind.function})
const riverpod = Riverpod();
```

##### Riverpodアノテーションの利用箇所

せっかくなのでRiverpodのコード上でどのようにRiverpodアノテーションが使われいるか軽く見てみます。
riverpod_analyzer_utilsというRiverpod内部で使われてるパッケージのriverpod_types.dartにTypeChekerがあるのでこれの参照をたどっていけば良さそうです。

```dart:riverpod_types.dart
/// Matches with the `Riverpod` annotation from riverpod_annotation.
const riverpodType =
    TypeChecker.fromName('Riverpod', packageName: 'riverpod_annotation');
```

https://github.com/rrousselGit/riverpod/blob/67d26d2a47a7351d6676012c44eb53dd6ff79787/packages/riverpod_analyzer_utils/lib/src/riverpod_types.dart#L11-L13

RiverpodAnnotationというクラスがあり、そこでriverpodTypeが使われています。
抽象構文木 (Abstract Syntax Tree: AST)をRiverpodアノテーションにパースしており、riverpodTypeかで判定しています。

```dart:riverpod_annotation.dart
class RiverpodAnnotation extends RiverpodAst {
  RiverpodAnnotation._({
    required this.annotation,
    required this.element,
    required this.keepAliveNode,
    required this.dependencies,
  });

  static RiverpodAnnotation? _parse(
    Declaration node,
  ) {
    final annotatedElement = node.declaredElement;
    if (annotatedElement == null) return null;

    for (final annotation in node.metadata) {
      final elementAnnotation = annotation.elementAnnotation;
      final annotationElement = annotation.element;
      if (elementAnnotation == null || annotationElement == null) continue;
      if (annotationElement is! ExecutableElement ||
          !riverpodType.isExactlyType(annotationElement.returnType)) {
        // The annotation is not an @Riverpod
        continue;
      }
```

RiverpodAnnotationはGeneratorProviderDeclarationのgetterの返り値に使われています。GeneratorProviderDeclarationはriverpod_generatorで生成されるProviderの元となるコードの宣言のようです。
GeneratorProviderDeclarationはabstract classになっており、ClassBasedProviderDeclarationとFunctionalProviderDeclarationが継承しています。

```dart:generator_provider_declaration.dart
abstract class GeneratorProviderDeclaration extends ProviderDeclaration {
  @override
  GeneratorProviderDeclarationElement get providerElement;
  RiverpodAnnotation get annotation;
```

ClassBasedProviderDeclarationとFunctionalProviderDeclarationもstaticな生成用メソッドがあり、ClassDeclarationかFunctionDeclarationをRiverpodAnnotation._parseに渡しています。パースに失敗してnullのときは早期returnしています。
これによりRiverpodアノテーションがついた生成元コードかを判定しています。
```dart
class ClassBasedProviderDeclaration extends GeneratorProviderDeclaration {
~~~

  static ClassBasedProviderDeclaration? _parse(
    ClassDeclaration node,
    _ParseRefInvocationMixin parent,
  ) {
    final element = node.declaredElement;
    if (element == null) return null;
    final riverpodAnnotation = RiverpodAnnotation._parse(node);
    if (riverpodAnnotation == null) return null;
~~~
class FunctionalProviderDeclaration extends GeneratorProviderDeclaration {
~~~

  static FunctionalProviderDeclaration? _parse(
    FunctionDeclaration node,
    _ParseRefInvocationMixin parent,
  ) {
    final element = node.declaredElement;
    if (element == null) return null;
    final riverpodAnnotation = RiverpodAnnotation._parse(node);
    if (riverpodAnnotation == null) return null;
```
Riverpodアノテーションによってriverpod_generator用の生成元コードかを判定する仕組みが分かりました🥳

##### おまけ　Riverpodアノテーションその他の利用箇所
riverpodTypeはriverpod_generatorパッケージ以外にも、riverpod_lintパッケージからも使われています。
例えば以下はRiverpodアノテーションがついたクラスにbuild methodが無いとき警告するlintルールです。registryに登録する際Riverpodアノテーションが無いときはreturnしていることが分かります。

```dart:notifier_build.dart
class NotifierBuild extends RiverpodLintRule {
  const NotifierBuild() : super(code: _code);

  static const _code = LintCode(
    name: 'notifier_build',
    problemMessage:
        'Classes annotated by `@riverpod` must have the `build` method',
  );

  @override
  void run(
    CustomLintResolver resolver,
    ErrorReporter reporter,
    CustomLintContext context,
  ) {
    context.registry.addClassDeclaration((node) {
      final hasRiverpodAnnotation = node.metadata.where(
        (element) {
          final annotationElement = element.element;

          if (annotationElement == null ||
              annotationElement is! ExecutableElement) return false;

          return riverpodType.isExactlyType(annotationElement.returnType);
        },
      ).isNotEmpty;

      if (!hasRiverpodAnnotation) return;
```

#### ProviderForアノテーション

RiverpodForというアノテーションがあります。こちらはdartdocで使うなと書いてあり、ユーザー向けではないようです👀

```dart:riverpod_annotation.dart
/// An annotation used to help the linter find the user-defined element from
/// the generated provider.
///
/// DO NOT USE
class ProviderFor {
```

ではどこで使われているかというと.g.dartで使われています。
以下のような生成元関数があるとき
```dart:hoge.dart
@riverpod
String hoge(HogeRef ref) {
  return 'hoge';
}
```
生成されたProviderに@ProviderFor(hoge)のように付与されます。
詳細は省きますが、ASTを解析するときにriverpod_generatorを使ってないProviderを見分けるのにこれが使われていました。（ProviderForアノテーションが付いてるならriverpod_generatorが生成したProviderと判定）
```dart:hoge.g.dart
/// See also [hoge].
@ProviderFor(hoge)
final hogeProvider = AutoDisposeProvider<String>.internal(
  hoge,
  name: r'hogeProvider',
  debugGetCreateSourceHash:
      const bool.fromEnvironment('dart.vm.product') ? null : _$hogeHash,
  dependencies: null,
  allTransitiveDependencies: null,
);

typedef HogeRef = AutoDisposeProviderRef<String>;
```

#### Rawアノテーション

Rawが最後のアノテーションです。docに２種類の使い方が説明されています。

```dart:riverpod_annotation.dart
/// {@template riverpod_annotation.raw}
/// An annotation for marking a value type as "should not be handled
/// by Riverpod".
///
/// This is a type-alias to [T], and has no runtime effect. It is only used
/// as metadata for the code-generator/linter.
///
/// This serves two purposes:
/// - It enables a provider to return a [Future]/[Stream] without
///   having the provider converting it into an [AsyncValue].
///   ```dart
///   @riverpod
///   Raw<Future<int>> myProvider(...) async => ...;
///   ...
///   // returns a Future<int> instead of AsyncValue<int>
///   Future<int> value = ref.watch(myProvider);
///   ```
///
/// - It can silence the linter when a provider returns a value that
///   is otherwise not supported, such as Flutter's `ChangeNotifier`:
///   ```dart
///   // Will not trigger the "unsupported return type" lint
///   @riverpod
///   Raw<MyChangeNotifier> myProvider(...) => MyChangeNotifier();
///   ```
///
/// The typedef can be used at various places within the return type declaration.
///
/// For example, a valid return type is `Future<Raw<ChangeNotifier>>`.
/// This way, Riverpod will convert the [Future] into an [AsyncValue], and
/// the usage of `ChangeNotifier` will not trigger the linter:
///
/// ```dart
/// @riverpod
/// Future<Raw<ChangeNotifier>> myProvider(...) async => ...;
/// ...
/// AsyncValue<ChangeNotifier> value = ref.watch(myProvider);
/// ```
///
/// {@endtemplate}
typedef Raw<T> = T;
```

##### Rawの使い方１
FutureやStreamをAsyncValueを使わず返す。
試してみましょう。
以下のようなにRawの有無が違う関数を用意すると
```dart:hoge.dart
@riverpod
Future<String> hoge(HogeRef ref) async {
  return 'hoge';
}

@riverpod
Raw<Future<String>> fuga(FugaRef ref) async {
  return 'fuga';
}
```
生成コードはAutoDisposeFutureProviderとAutoDisposeProviderで確かに違いました！
```dart:hoge.g.dart
// See also [hoge].
@ProviderFor(hoge)
final hogeProvider = AutoDisposeFutureProvider<String>.internal(
  hoge,
  name: r'hogeProvider',
  debugGetCreateSourceHash:
      const bool.fromEnvironment('dart.vm.product') ? null : _$hogeHash,
  dependencies: null,
  allTransitiveDependencies: null,
);

typedef HogeRef = AutoDisposeFutureProviderRef<String>;
String _$fugaHash() => r'18dcf3ebf332c9ef412a0feb131a9b372c683131';

/// See also [fuga].
@ProviderFor(fuga)
final fugaProvider = AutoDisposeProvider<Future<String>>.internal(
  fuga,
  name: r'fugaProvider',
  debugGetCreateSourceHash:
      const bool.fromEnvironment('dart.vm.product') ? null : _$fugaHash,
  dependencies: null,
  allTransitiveDependencies: null,
);

typedef FugaRef = AutoDisposeProviderRef<Future<String>>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
```

##### Rawの使い方２
riverpod_generatorはChangeNotifeirを継承したクラスなどをサポートしていません。
以下のriverpod_lintのルールで警告されます。

https://pub.dev/packages/riverpod_lint#unsupported_provider_value-riverpod_generator-only

GoRouterなどもChangeNotifier継承しているためこのルールに引っかかります。
この際Rawを使うことで解決できます。
```dart:READMEの例
@riverpod
Raw<GoRouter> myRouter(MyRouterRef ref) {
  final router = GoRouter(...);
  // Riverpod won't dispose the ChangeNotifier for you in this case. Don't forget
  // to do it on your own!
  ref.onDispose(router.dispose);
  return router;
}
```

## まとめ
riverpod_annotationのRiverpod、ProviderFor、Rawアノテーションについて説明しました🙌