flutter_riverpodの@riverpodを使ってみたい
何をしたいか
riverpodのgithubを見ると最近@riverpod
とつけているのが増えている気がします。
ただriverpodのページを見ても既存のProviderを作成する方法しか載っていないように見えましたが、ページの上部にThe documentation for version 2.0 is in progress. A preview is available at: https://docs-v2.riverpod.dev
という文言を発見。
どうやら新しいドキュメントを作成しているようなので、それを見ると@riverpod
の説明などありそうなのでドキュメントを読み込んでみる。
セットアップ
以下、パッケージを追加する。
flutter pub add flutter_riverpod dev:custom_lint dev:riverpod_lint riverpod_annotation dev:build_runner dev:riverpod_generator
flutter_rivderpod
はいつも追加しているが、custom_lint``riverpod_list``rivderpod_annotation``riderpod_generator
は使ったことはなかったです。とりあえず言われるがままに追加しています。
custom_lint/riverpod_lint
custom_lint
とriverpod_lint
はセットで使うっぽいです。(custom_lint
の機能でriverpod_list
を使うイメージ?🤔)
analysis_options.yaml
のファイルに以下を追記する。
analyzer:
plugins:
- custom_lint
こうするとIDE上に警告が出るようになるようでした。
まだいつもはflutter analyze
のコマンドを実行して静的解析をしているけども、custom_lint
を利用する場合はdart run custom_lint
コマンドを実行するようでした。
~/D/w/r/reverpod_test ❯❯❯ dart run custom_lint
lib/main.dart:4:3 • Flutter applications should have a ProviderScope widget at the top of the widget tree. • missing_provider_scope
runApp内でProviderScopeを宣言していない的なWarning。
確かにriverpodを利用する前提の静的解析をするようになっていそう👍
とにかく@riverpodを使ってみる
サンプルに書いてあったことをそのまま実装してみます。
part 'main.g.dart'; // build_runnerで出力されるファイル
@riverpod
String helloWorld(HelloWorldRef ref) {
return 'Hello world';
}
freezedなどを利用している場合は馴染み深いですが、アノテーションを利用するためbuild_runner
のコマンドを実行するようでした。
そのためにpart 'main.g.dart'
を宣言し、Hello World
の文字列を返却する関数に@riverpod
をつけました。
その状態でいつものコマンドを実行します。
$ dart run build_runner build -d
helloWorldがどんな形に変換され、出力されたのかを確認してみます。
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$helloWorldHash() => r'8bbe6cff2b7b1f4e1f7be3d1820da793259f7bfc';
/// See also [helloWorld].
@ProviderFor(helloWorld)
final helloWorldProvider = AutoDisposeProvider<String>.internal(
helloWorld,
name: r'helloWorldProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$helloWorldHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef HelloWorldRef = AutoDisposeProviderRef<String>;
// ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions
色々実装されているようですが、結果的には以下のhelloWorldProvider
の実装をしたのと同じかんじだそうでした。
final helloWorldProvider = AutoDisposeProvider(
(ref) => 'Hello world',
);
参照方法は以下のような感じで、自前で宣言したときのProviderの使いかたと同じでした。
Consumer(
builder: (_, ref, __) {
final helloWorld = ref.watch(helloWorldProvider);
return Text(helloWorld);
},
),
クラス定義に@rivderpodをつけてみる
以下のクラスを宣言し、@riverpod
をつけてコードを出力してみます。
@riverpod
class Counter extends _$Counter {
@override
int build() => 0;
void increment() => state++;
}
↓ dart run build_runner build -d
typedef HelloWorldRef = AutoDisposeProviderRef<String>;
String _$counterHash() => r'4243b34530f53accfd9014a9f0e316fe304ada3e';
/// See also [Counter].
@ProviderFor(Counter)
final counterProvider = AutoDisposeNotifierProvider<Counter, int>.internal(
Counter.new,
name: r'counterProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$counterHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$Counter = AutoDisposeNotifier<int>;
想定通りcounterProvider
が生成されました。
ただStateProviderとかではなく、NotifierProviderというものが生成されていました。
参照するときは
final count = ref.watch(counterProvider);
関数を実行するときは
ref.read(counterProvider.notifier).increment();
NotifierProvider以外のProvider
StateNotifierProvider
とChangeNotifierProvider
は非推奨になっているようで、NotifierProvider
の利用を検討する必要があるようでした。
SteamProvider
通信などでよく利用されるSteamProvider
は以下のように定義すれば作成されました。
Stream
に反応しているようです。
@riverpod
Stream<List<String>> chat(ChatRef ref) async* {
final socket = await Socket.connect('my-api', 4343);
ref.onDispose(socket.close);
var allMessages = const <String>[];
await for (final message in socket.map(utf8.decode)) {
allMessages = [...allMessages, message];
yield allMessages;
}
}
FutureProvider
こちらも通信でよく利用されるFutureProvider
を生成するためのコードになります。
Future
に反応してFutureProvider
になるようです。
@riverpod
Future<User> fetchUser(FetchUserRef ref, {required int userId}) async {
final json = await http.get('api/user/$userId');
return User.fromJson(json);
}
StateProvider
通信中のIndicatorの表示制御だったり、画面全体の状態などを管理するときなどによく利用する(と思っている)StateProviderを作成する場合は@riverpod
は利用できず、既存の作成方法で実装する必要があるとのことでした。
final counterProvider = StateProvider<int>((ref) => 0);
void _incrument() {
// update関数を使用することでインクリメントなどの処理はより簡潔にかけたりしそうです。
ref.read(counterProvider.notifier).update((state) => state + 1);
}
いろいろ試してみる
同じ名前のProviderになりそうな宣言をしてみる
@riverpod
class Counter extends _$Counter {
@override
int build() => 0;
void increment() => state++;
}
@riverpod
int counter(CounterRef ref) => 0;
両方ともcounterProvider
になりそうな宣言でコードを生成してみます。
そうすると見事にcounterProvider
が2つ生成されました。
コード生成自体は成功しますが、静的解析やコンパイル時にコケそうな感じでした。
複数のProviderを一個のProviderで参照してみる
@riverpod
int countA(CountARef _) => 5;
@riverpod
int countB(CountBRef _) => 8;
@riverpod
int countAB(CountABRef ref) {
final countA = ref.watch(countAProvider);
final countB = ref.watch(countBProvider);
return countA * countB;
}
WidgetRef
ではなく、CounterARef
などいつもと違う型名でRefを宣言していましたがいつも通りWidgetRef
の感覚で利用してもよさそうでした。
Discussion