🌊
Riverpod 3.0 最新プレビュー (06.17時点)
Remi氏 FlutterNinjas Tokyo2024登壇の為、初来日🇯🇵
6月13-14日に東京で行われた日本初のグローバルFlutterカンファレンス「FlutterNinjas Tokyo2024」にRiverpodの開発者として著名なRemi Rousselet氏が来日。
年内にリリース予定のRiverpod 3.0に追加される機能についてセッションを行いました。以下はそのセッション内容の抜粋となります。
1. 冗長なタイプを削除
- ProviderやNotifierのAutoDispose subclassesはなくなる
- Refのsubclassesもなくなる
- StateNotifier, StateProviderなどはlegacy packageに移管
2. Generic providers
- Generic typeを受け取るProviderの定義が可能に
- 以下のように定義時に型を指定するProviderを作成可能
class MyListNotifier<T> exetends _$MyListNotifier<T>{
List<T> build() => [];
}
- code generation
では対応していないでのみ対応 - より複雑なジェネリクスにも対応
Pair<A,B> pair<A extends num, B extends Object>(
MyListRef<T> ref,
A a,
B b,
){
returns Pair(a,b);
}
3. Scoped Providerへの破壊的変更
- Scoped providerはver3より
dependencies
オプションの記述が必須となる - Scoped Providers: オーバーライドされることが前提のProvider
- 以前はwidgetツリーどこからでも実装しProviderをオーバーライドすることが出来た
ProviderScope
は今後はトップレベルでのみ使用可能に - トップレベル以外でオーバーライドしたいProviderはProvider定義時に
dependencies: []
アノテーションを使って、オーバーライド可能なProviderである事を明示する必要がある - Providerがオーバーライドされるか否かの選択肢を持たない事でパフォーマンスの改善が見込まれる
(dependencies: [])
int b(ref) => 0;
void main(){
runApp(
ProviderScope(
overrides:[
bProvider.overrideWithValue(42),
]
),
child: ProviderScope(
overrides:[
bProvider.overrideWithValue(42),
]
),
);
}
- これらは
riverpod_lint
のprovider_dependencies
ルールでの検知も可能になる - Scoped Providerはより簡潔に記述することも可能
// Full
(dependencies: [])
class MyNotifier{
int build(){
throw UnimplementedError();
}
}
// Simpler
class MyNotifier{
int build();
}
4. FmailyProvider引数へのサポート
- FamilyProviderを複数のwidgetで使用する場合、渡す引数を複数のwidgetに用意する必要があったが、上記のScopedProviderへの変更を応用する事でより簡潔に記述する事が可能になった
- 今までは、FamilyProvider1つ1つに引数を渡す必要があった
myFamily(MyFamilyRef ref, {required int id}) => 0;
class Example extends ConsumerWidget{
Example({super.key, required this.id});
final int id;
Widget build(context, ref){
ref.watch(
myFamilyProvider(id: id),
);
}
}
int
- しかし今後はDependenciesアノテーションにてoverrideするFamilyProviderを引数なしで記述する事で、ProviderScope側で引数を定義するだけで良い
Widget build(context){
return ProviderScope(
overrides: [
myFamilyProvider.overrideWithDefault(id:id)
],
child: Example(),
)
}
([myFamily])
class Example extends ConsumerWidget{
Example({super.key});
Widget build(context, ref){
ref.watch(myFamilyProvider);
}
}
if(mounted)
の追加
5. - Provider内で
mounted
のチェックが可能に
class Example extends _$Example{
int build() => 0;
Future<void> asyncMethod() async {
await something();
if(!mounted) return;
}
}
6. テスト時の記述の簡素化
- Providerのテストが
ProviderContainer.test()
でより簡潔に書けるように
BEFORE
ProviderContainer createContainer({
List<ProviderOverride>? overrides,
}) {
final container = ProviderContainer(overrides: []);
addTearDown(container.dispose);
return container;
}
test('example', (){
final container = createContainer();
...
})
AFTER
test('example', (){
final container = ProviderContainer.test();
...
})
7. ref.listenを初期化不要に
- ref.listenをProviderの初期化無しに定義可能に
- ref.listen内の
weak
パラメータに渡すbooleanで機能をオンオフ -
weak: true
の場合、listenの時点で初期化されていなくても、後々変更があった際に検知する事が可能 - ログイン状態の監視などに有効
another(AnotherRef ref){
ref.listen(exampleProvider, weak: true, (prev, next){
...
});
}
int
8. Side-effectのサポート強化
- POSTなどサーバーへの変更を伴う処理(side-effect)の状態に応じたUIの変更は以前から課題があったが、この度新しいミューテーションメソッドを追加
- (side-effectの課題についてはこちら)
- Notifierに
@mutation
アノテーションを付けたstaticメソッドを実装 - 返り値として新しい状態値を返す形に記述
class TodoList extends _$TodoList{
static Future<List<Todo>> addTodo(
MutationRef<TodoList> ref,
Todo todo,
){
final response = await http.get(
'your-api/todos/new',
todo.toJson(),
);
return (response as List) // 新しい状態値
.map(Todo.fromJson)
.toList();
}
- UI側ではミューテーションメソッドが返す
MutationState
オブジェクトを監視し、その状態値に応じたUI処理を記述
class SuperButton extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final mutation = ref.watch(todoNotifier.addTodo);
return switch (MutationState addTodo) {
EmptyMutationState() => ElevatedButton(
onPressed: () => addTodo(Todo('New todo!')),
child: Text('Add todo'),
),
LoadingMutationState() => ElevatedButton(
onPressed: null,
child: CircularProgressIndicator(),
),
ErrorMutatationState() => ElevatedButton(
onPressed: addTodo.retry,
style: ButtonStyle(
backgroundColor: WidgetStateProperty.all(Colors.red),
foregroundColor: WidgetStateProperty.all(Colors.white),
),
child: Text('Error. Retry?'),
),
SuccessMutationState() => ElevaedButton(
onPressed: null,
child: Icon(Icons.check),
),
};
}
}
- これにより下記のようにProviderをwatchすることで、UIの別の箇所でもミューテーションの状態値を監視する事ができ、複数のUIで同一の状態値に基づいた表示を行う事が可能
class SuperAppBar extends ConsumerWidget{
Widget build(context, ref){
final addTodo = ref.watch(todoListProvider.notifier);
}
}
9. 自動リトライ
- Provider内でExceptionをthrowされると自動でリトライが実行されるように
example() {
print(DateTime.now().seconds);
throw Exception();
}
// 実行結果:1,2,4,8
int
- カスタムのリトライロジックを追加したい場合はProviderに対し
@Riverpod(retry: myRetry)
で渡す事が可能
(retry: myRetry)
int example(ExampleRef ref){
print(DateTime.now().seconds);
throw Exception();
}
Duration? myRetry(int retry Count, Object error){
if(retryCount > 10) return null;
return Duration(
seconds: 1 + retryCount * retry Count,
)
}
10. オフライン・キャッシング
- Providerの値をデバイスに直接キャッシュすることが可能になる
- 別パッケージに定義されるローカルDB(ex. SharedPreference, SQLite, etc.)クラスをProviderScopeにバインディング
ProviderScope(
offlineConnector: const SharedAppPreferenceAsJson(),
)
- 必要なシリアライズメソッドをモデルクラスに追加(ex. fromJson, toJson, etc.)
class Product{
factory Product.fromJson(Map<String, Object?> json){
...
}
Map<String, Object?> toJson() => ...
}
- Provider側にオフライン時の保存先となるテーブル名をアノテーションに定義
(offline: '<name-table>')
Future<List<Product>> products(ProductRef ref){
final response = http.get('my-api/products');
return (response as List).map(Product.fromJson).toList();
}
- ローカルDB上のテーブル定義に変更が生じる=モデル定義に変更が発生する場合は
destroyKey
オプションを追加することで、新しいスキーマ定義のデータをキャッシュさせる事が可能になる
(offline: '<name-table>', destroyKey: 'abc')
Future<List<Product>> products(ProductRef ref){
final response = http.get('my-api/products');
return (response as List).map(Product.fromJson).toList();
}
FlutterNinjas Tokyoについて🥷
FlutterNinjas Tokyoは日本初のFlutterに特化したグローバルカンファレンスです。今年が初開催となり、プラチナスポンサーにCode Magic、ゴールドスポンサーにMoneyForwardを迎え、登壇者、来場者共に国内外より130名以上のFlutter開発者が参加しました。全セッションおよびワークショップは英語で行われ、今までにない刺激を日本のFlutter開発者に与える事をミッションとしています。
本記事のようにどこよりも早く最新情報を入手出来たり、憧れの開発者、国内外のFlutter開発者と交流したり、素敵なコミュニティを作り上げていきたいと思っています。
イベントのセッション動画も配信予定&来年も開催予定です。是非ともフォローお願いします🙌🥳
Discussion
申し訳ありません。自分のミスで「2. Generic Providers」について一点誤っていた箇所があるので修正させて頂きました🙇🙇🙇🙇
code generationでは対応していない❌ → code generationでのみ対応⭕️