先日、freezedでハマって小一時間を無駄に過ごしてしまったのでここに書いて供養したいと思います。
今やってるお仕事ではriverpodを使っていて、screenに対してview_modelを作成し、state管理を行っています。
view_modelの持っているstateをfreezedで作っているのですが、そこで値の変更ができなくてはまった話です。
class HomeScreen extends ConsumerWidget { const HomeScreen({Key? key}) : super(key: key); @override Widget build(BuildContext context, WidgetRef ref) { final viewModel = ref.watch(homeViewModelProvider); return viewModel.when( data: (state) => Scaffold(// 省略), error: (_, __) => ErrorView(), loading: () => const Center(child: CircularProgressIndicator())); } }
@riverpod class HomeViewModel extends _$HomeViewModel { @override Future<HomeState> build() { return HomeState(); } } @freezed class HomeState with _$HomeState { const factory HomeState() = _HomeState; }
と、こんな感じでfreezedを使っています。
ここで、HomeStateは何か値を持ち、ボタンをタップされたらViewModelで値を変更するという実装があるとします。
@riverpod class HomeViewModel extends _$HomeViewModel { @override Future<HomeState> build() { return HomeState(hogehoge: 'hogehoge'); } Future<void> onTapButton({required String fugafuga}) async { final currentState = await future; state = await AsyncValue.guard(() async { return currentState.copyWith(hogehoge: fugafuga); }); } } @freezed class HomeState with _$HomeState { const factory HomeState({ required String hogehoge, }) = _HomeState; }
と、こんな感じでhogehogeの値を変更しようとした時にエラーが起きました。
エラーの内容は
Null check operator used on a null value
同じような実装は過去にいくつもしているので、なぜここだけこんなエラーが出るのか分からずに半日くらいコードと睨めっこしてしまいました。
結論としては、HomeStateの書き方に問題がありました。
実際に私が書いていたコードはこうでした。
@freezed class HomeState with _$HomeState { const factory HomeState({ required String hogehoge, required}) = _HomeState; }
お分かりいただけたでしょうか?
hogehogeの次に謎のrequiredがありました。
場所が場所だけにこの謎のrequiredが書かれている事に気づかなかった事。
そして、静的コンパイルでもエラーが出ずにジェネレートもできてしまうんですよね。
さらに、初期値は普通に入れれるので初回の画面表示は問題なくできてしまうんですよね。
これが邪魔して、nullの値があるよっていうエラーがで続けていました。
そんなこんなでfreezedを使う時には余計なコードがないか気をつけようねっていう話でした。