くらげになりたい。

くらげのようにふわふわ生きたい日曜プログラマなブログ。趣味の備忘録です。

Flutterのhooks_riverpod/flutter_hooksに再入門する

久々に見直してたら、@riverpodを使って簡潔にProviderが書けるようになっている。
Stateとかを書かなくて良くなったけど、いろいろ忘れているので、
再度入門したときの備忘録(*´ω`*)

参照した公式ドキュメントはこのあたり。

インストール

$ fvm flutter pub add flutter_hooks hooks_riverpod riverpod_annotation

$ fvm flutter pub add -d riverpod_generator build_runner
$ fvm flutter pub add -d custom_lint riverpod_lint

pubspec.yamlはこんな感じ。

# pubspec.yaml
environment:
  sdk: ">=3.0.5 <4.0.0"

dependencies:
  flutter:
    sdk: flutter

  freezed_annotation: ^2.2.0
  flutter_hooks:
  hooks_riverpod: ^2.3.6
  riverpod_annotation: ^2.1.1
dev_dependencies:
  flutter_test:
    sdk: flutter

  flutter_lints: ^2.0.0
  freezed: ^2.3.5
  build_runner: ^2.4.6
  json_serializable: ^6.7.1
  riverpod_generator: ^2.2.3

analysis_options.yamlの設定

# analysis_options.yaml
analyzer:
  plugins:
    - custom_lint

Widgetの種類

  • Flutterの基本Widget
    • StatelessWidget ... 状態なし
    • StatefulWidget ... 状態あり
  • HookConsumerWidget from hooks_riverpod
    • = ConsumerWidget + HookWidget
      • HookWidget = hookが使えるStatelessWidget from flutter_hooks
      • ConsumerWidget = providerが使えるStatelessWidget from flutter_riverpod
    • 基本はこれを使う
  • StatefulHookConsumerWidget from hooks_riverpod
    • StatefulWidgetのライフサイクルメソッドが使えるHookConsumerWidget

@riverpodを使った書き方

@riverpodを使って簡潔にProviderが書けるようになっている。

// providers.dart
import "package:riverpod_annotation/riverpod_annotation.dart";

part 'providers.g.dart';

// data-model
class Todo {
  Todo(this.description, this.isCompleted);
  final bool isCompleted;
  final String description;
}

// stateをもつProvider
// (= NotifierProvider.autoDispose/AutoDisposeNotifier)
// todosProviderを自動生成
@riverpod
class Todos extends _$Todos {
  @override
  List<Todo> build() {
    return []; // stateの初期値
  }

  void addTodo(Todo todo) {
    state = [...state, todo];
  }
}

// stateを持たないデータを加工するだけのProvider
// (= Provider.autoDispose)
// completedTodosProviderを自動生成
@riverpod
List<Todo> completedTodos(CompletedTodosRef ref) {
  final todos = ref.watch(todosProvider);

  // we return only the completed todos
  return todos.where((todo) => todo.isCompleted).toList();
}

あとは、build_runnerを実行すると、自動生成してくれる。

$ fvm flutter pub run build_runner build --delete-conflicting-outputs
$ fvm flutter pub run build_runner watch --delete-conflicting-outputs

# うまくいかないときには、cleanしてから再度buildするとよい
$ fvm flutter pub run build_runner clean
$ fvm flutter pub run build_runner build --delete-conflicting-outputs

自動生成できたら、こんな感じでHookConsumerWidgetから呼び出せる。

import 'providers.dart';
class HomePage extends HookConsumerWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final provider = ref.watch(completedTodosProvider);
    
    // ...
  }
}

書き方の規則

規則的にはこんな感じ。

<filename><ClassName><functionName>は、
それぞれ合わせないと自動生成に失敗する。

// <filename>.dart
import "package:riverpod_annotation/riverpod_annotation.dart";

part '<filename>.g.dart';

// <className>Providerを生成
@riverpod
class <ClassName> extends _$<ClassName> {
  @override
  List<T> build() {
    return // ...
  }
}

// <functionName>Providerを生成
@riverpod
List<T> <functionName>(<FunctionName>Ref ref) {
  return // ...
}

class/functionの他に、return typeで使われるProviderが異なる。

他のパターン

他の書き方は以下のMigrate from non-code-generation variantに書いてあるので、
一読するのをおすすめ。

また、左のメニューにあるThird party examplesもかなり増えているので、
とても参考になった。


以前よりもかなり簡潔に書けるようになっていてすごい(*´ω`*)