riverpod_cliを利用してriverpod v1.0.0へ移行してみた
はじめに
v1.0.0
リリースについて
riverpod
のv1.0.0
が、遂に 2021-11-05(JST)
に公開されました。
Flutterの状態管理戦争に終止符を打つ(?)勢いのriverpod
のv1.0.0
リリースは、下記のCHANGELOGを見ても変更箇所が多めです。
又、v1.0.0
では、後方互換のない変更が発生している為、そのままバージョンアップを行うとエラーになる可能性があり、公式からもriverpod_cli
という移行ツールが公開されています。
今回はこのriverpod_cli
を利用して、どれほどスムーズに v1.0.0
への移行が行えるか確認してみました。
方針
移行対象のアプリを作成する
今回は移行対象のサンプルアプリとして、試験的にv0.14.*
系のriverpod
を使用したサンプルアプリを作成しました。今回はこちらのサンプルアプリにriverpod_cli
を適用し結果を確認したいと思います。
こちら↓がv0.14.*
で作成した移行対象の「入力された文字数を右上に表示する」アプリです。
サンプルでは、状態管理にriverpod/state_notifier
を利用し、hooks
を取り入れた hooks_riverpod
を使用しています。
flutter_hooks | hooks_riverpod |
---|---|
^0.16.0-nullsafety.0 | ^0.14.0+3 |
import 'package:hooks_riverpod/hooks_riverpod.dart';
final memoStateNotifierProvider =
StateNotifierProvider<MemoStateNotifier, Memo>((ref) {
return MemoStateNotifier(Memo.empty());
});
class Memo {
Memo({required this.memo, required this.wordCount});
final String memo;
final String wordCount;
factory Memo.empty() {
return Memo(memo: '', wordCount: '');
}
}
class MemoStateNotifier extends StateNotifier<Memo> {
MemoStateNotifier(Memo state) : super(state);
void update({required String memo}) {
state = Memo(
memo: memo,
wordCount: memo.isEmpty ? '' : 'count:${memo.length}');
}
}
画面側は HookWidget
を継承し、画面右上のcount:12
というTextのみ再描画したい為、HookBuilder
を利用しています。
class MemoPage extends HookWidget {
const MemoPage({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Scaffold(appBar: _appBar(), body: _body());
}
PreferredSizeWidget _appBar() {
return AppBar(
title: const Text('MemoPad'),
);
}
Widget _body() {
return Padding(
padding: const EdgeInsets.all(10),
child: Column(
children: [
_header(),
_editor(),
],
),
);
}
Widget _header() {
return HookBuilder(builder: (BuildContext context) {
return Align(
alignment: Alignment.centerRight,
child: Text(
useProvider(
memoStateNotifierProvider.select((state) => state.wordCount)),
style: TextStyle(color: Theme.of(useContext()).disabledColor),
),
);
});
}
Widget _editor() {
var notifier = useProvider(memoStateNotifierProvider.notifier);
return Expanded(
child: TextField(
maxLines: null,
textAlignVertical: TextAlignVertical.top,
expands: true,
keyboardType: TextInputType.multiline,
decoration: const InputDecoration(
isDense: true,
border: OutlineInputBorder(),
),
onChanged: (memo) => notifier.update(memo: memo)),
);
}
}
実施
riverpod_cli を実行する
それでは移行を実行します。
移行方法は公式通り、 まずはriverpod_cli
の導入から行います。
dart pub global activate riverpod_cli
導入自体は問題なく完了し、次に下記のコマンドよりmigrationを実行します!
riverpod migrate
移行が必要な箇所は、次のように都度、確認してくれます。
- import 'package:riverpod/all.dart';
+ import 'package:riverpod/riverpod.dart';
適用については Accept change(y = yes, n = no [default], A = yes to all, q = quit)
の何れかを選択して実行していきます。
今回はサンプルアプリだった事もありますが、移行は1分もかからずに完了しました。
ツールのみで移行した直後のものは、こちら↓になります。
検証
結果を確認する
移行結果を確認すると、v1.0.0
で変更されたコマンド名を単純に置換しているだけでなく、メソッドへのパラメータ追加などややロジカルな移行も行われているようでした。
例えば、今回のサンプルアプリは、v1.0.0
では削除されるuseProvider
を利用していましたが、build
メソッド直下で使用しているのはなく、少し深い build() -> _body() -> _editor()
という孫階層で useProvider
を利用していました。
メソッド呼び出し元で ref
パラメータの提案が行われ...
- _editor(),
+ _editor(ref),
呼び出し先にもWidgetRef ref
パラメータの追加が行われていました!
- Widget _editor() {
- var notifier = -useProvider(memoStateNotifierProvider.notifier);
+ Widget _editor(WidgetRef ref) {
+ var notifier = ref.watch(memoStateNotifierProvider.notifier);
他にも、気になっていたuseProvider
はref.watch
へと変更され、 HookWidget
-> HookConsumerWidget
への移行なども自動で行ってくれており、公式の変更内容にそってmigrateが行われているようです。
手動で移行する
Terminalに下記のログが出力されていました。
The following files were not able to be successfully migrated
/lib/memo_page.dart: had error when visiting a method invocation memoStateNotifierProvider.select((state) => state.wordCount)
Null check operator used on a null value
確認すると、下記のように引数名が重複している箇所(_headr
の引数ref
と、HookConsumer
のref
)がありエラーが発生しているようです。
Widget _header(WidgetRef ref) {
return HookConsumer(builder: (BuildContext context, ref, child) {
ここは _header
の引数 ref
が重複して不要の為、手動でパラメータ自体を削除しました。
Widget _header() {
return HookConsumer(builder: (BuildContext context, ref, child) {
又、pubspec
の flutter_hooks
もバージョンが更新されておらず、下記のエラーが出力されていました。
Because hooks_riverpod >=1.0.0-dev.4 depends on flutter_hooks ^0.18.0 and notepad depends on flutter_hooks ^0.16.0-nullsafety.0, hooks_riverpod >=1.0.0-dev.4 is forbidden.
riverpod
は、複数のパッケージ(riverpod
flutter_hooks
flutter_riverpod
)が公開されていますが、インストールするべきパッケージの組み合わせとVersionは公式ドキュメントの下記に記載されているので、flutter_hooks
のバージョンを ^0.18.0
に指定しました。
又、useProvider
を使用してメソッドを実行していた下記箇所が、ref.watch
に移行された点も気になりました。
- Widget _editor() {
- var notifier = useProvider(memoStateNotifierProvider.notifier);
+ Widget _editor(WidgetRef ref) {
+ var notifier = ref.watch(memoStateNotifierProvider.notifier);
状態
ではなく 処理
を参照しているので、監視(ref.watch)
だと リビルドが発生してしまうのではと思ったのですが、公式ドキュメントにも DON'T use ref.read inside the build method
と「build内では ref.read
を使用してはいけない」と書かれています。
ref.watch
のままで、本当にリビルドがされないのか確認してみました。
Widgetのリビルド回数を確認する為には、AndroidStudioを使います。
入力文字が変更された場合、stateが変更されリビルドが発生しますが、ref.watch
利用しているWidgetのリビルド回数は増えておらず、移行後の結果には問題ありませんでした。
StateNotifierで状態
と処理
が分離しており、ref.watch
の対象(****.notifier
)には変化する 状態
が含まれていない為、わざわざ ref.read
or ref.watch
を使い分けなくても ref.watch
で統一できるようです。
さいごに
riverpod_cli
実行後にエラーは発生したものの、手動で変更したcommitは下記程度となり、非常に少ない修正でした。
今回はサンプルアプリを作成し、移行ツールを実行したのですが、フルオートで移行できるというわけではなく、必ず自身での確認・修正が必要だと感じました。
只、riverpod
公式のドキュメントは非常に丁寧にv1.0.0
に合わせてアップデートされている為、事前に変更内容を把握しておくと、移行ツールの提案内容が理解しやすく、移行作業がスムーズに行えるかと思います!
Discussion