Riverpod 3.0とFreezed 3.0への移行で行った作業まとめ
はじめに
FlutterプロジェクトでRiverpod 2.x → 3.x、Freezed 2.x → 3.xへのバージョンアップを行いました。この記事では、移行時に実施した主な変更点と対応方法をまとめます。
バージョン情報
- Riverpod: 2.x → 3.1.0
- Freezed: 2.x → 3.2.3
- hooks_riverpod: 3.1.0
- riverpod_annotation: 2.3.5
主な変更点
1. Riverpod 3.0への移行
1.1 StateNotifier → Notifier/AsyncNotifierへの移行
Riverpod 3.0では、StateNotifierが非推奨となり、NotifierとAsyncNotifierに置き換えられました。
Before (Riverpod 2.x)
class LoadingController extends StateNotifier<bool> {
LoadingController() : super(false);
void show() {
state = true;
}
void dismiss() {
state = false;
}
}
final loadingProvider = StateNotifierProvider<LoadingController, bool>(
(ref) => LoadingController(),
);
After (Riverpod 3.x)
class LoadingController extends Notifier<bool> {
@override
bool build() => false;
void show() {
state = true;
}
void dismiss() {
state = false;
}
}
final loadingProvider = NotifierProvider<LoadingController, bool>(() {
return LoadingController();
});
1.2 AsyncNotifierの使用
非同期処理を行う場合はAsyncNotifierを使用します。
class BuyItemController extends AsyncNotifier<BuyItemState> {
final _buyItemService = BuyItemService();
@override
Future<BuyItemState> build() async {
final buyItemList = await _buyItemService.fetchBuyItems();
return BuyItemState(buyItemList: buyItemList);
}
Future<void> fetchBuyItems() async {
state = const AsyncValue.loading();
try {
final buyItemList = await _buyItemService.fetchBuyItems();
state = AsyncValue.data(BuyItemState(buyItemList: buyItemList));
} catch (e, stackTrace) {
state = AsyncValue.error(e, stackTrace);
}
}
}
final buyItemControllerProvider =
AsyncNotifierProvider<BuyItemController, BuyItemState>(() {
return BuyItemController();
});
1.3 FamilyNotifierの扱い
FamilyNotifierを使用する場合、ファクトリー関数のシグネチャが変更されました。
Before
final shopEditProvider = StateNotifierProvider.family<
ShopEditController, ShopEditState, String>(
(ref, shopId) => ShopEditController(shopId: shopId),
);
After
final shopEditProvider = NotifierProvider.family<
ShopEditController, ShopEditState, String>(
(shopId) {
final controller = ShopEditController();
Future.microtask(() => controller.init(shopId));
return controller;
},
);
1.4 StateProviderの扱い
Riverpod 3.0では、StateProviderがriverpodパッケージから直接エクスポートされなくなりました。hooks_riverpodから提供されていますが、より明示的な方法としてNotifierProviderを使用することもできます。
Before
final cityListFilterPrefIdProvider = StateProvider<String?>((ref) => null);
After (Option 1: hooks_riverpodから使用)
import 'package:hooks_riverpod/hooks_riverpod.dart';
final cityListFilterPrefIdProvider = StateProvider<String?>((ref) => null);
After (Option 2: NotifierProviderを使用)
class CityListFilterPrefIdController extends Notifier<String?> {
@override
String? build() => null;
void setValue(String? value) {
state = value;
}
}
final cityListFilterPrefIdProvider =
NotifierProvider<CityListFilterPrefIdController, String?>(() {
return CityListFilterPrefIdController();
});
1.5 sealed classの追加
Riverpod 3.0では、状態クラスにsealed classを使用することが推奨されます(Freezed 3.0の要件でもあります)。
@freezed
sealed class LoadingState with _$LoadingState {
const factory LoadingState({
@Default(false) bool isLoading,
}) = _LoadingState;
}
2. Freezed 3.0への移行
2.1 sealed classの必須化
Freezed 3.0では、@freezedアノテーションを使用するクラスはsealed classである必要があります。
Before (Freezed 2.x)
@freezed
class ShopModel with _$ShopModel {
const factory ShopModel({
@Default('') String id,
@Default('') String name,
}) = _ShopModel;
factory ShopModel.fromJson(Map<String, dynamic> json) =>
_$ShopModelFromJson(json);
}
After (Freezed 3.x)
@freezed
sealed class ShopModel with _$ShopModel {
const factory ShopModel({
@Default('') String id,
@Default('') String name,
}) = _ShopModel;
factory ShopModel.fromJson(Map<String, dynamic> json) =>
_$ShopModelFromJson(json);
}
2.2 一括修正の実施
プロジェクト内の全ての@freezedクラス(約38クラス)をsealed classに変更しました。
# 修正が必要なファイルを検索
grep -r "@freezed" lib/models --include="*.dart" -A 2
# 各ファイルで class → sealed class に変更
注意点
-
build_runnerの実行
- Freezed 3.0では、
sealed classを使用するため、コード生成を必ず実行する必要があります - エラーが発生した場合は、生成ファイルを削除してから再生成してください
- Freezed 3.0では、
-
StateProviderの扱い
-
StateProviderはhooks_riverpodから提供されていますが、より明示的なNotifierProviderの使用も検討してください
-
-
FamilyNotifierの初期化
-
build()メソッドは引数を受け取れないため、初期化処理は外部で行う必要があります -
Future.microtask()を使用して初期化を遅延実行する方法が有効です
-
まとめ
Riverpod 3.0とFreezed 3.0への移行は、主に以下の変更が必要でした:
-
StateNotifier→Notifier/AsyncNotifierへの置き換え - 全ての
@freezedクラスをsealed classに変更
これらの変更により、より型安全で保守性の高いコードになりました。移行作業は大変でしたが、新しいAPIの恩恵を受けることができました。
Discussion