Riverpod+Flutter Hooks+freezedで、カウンターアプリを作ってみた
はじめに
僕は今までProvider + ChangeNotifierをメインとして使っていましたが、どうやらRiverpod + Flutter Hooks + freezedがモダンな書き方らしいので、理解を深めるためにカウンターアプリを作ってみました。
コードはGithubにあげています。
初期画面 | 一回ボタンを押すと |
---|---|
構成
pubspec.yaml
- dependencies
flutter_hooks: ^0.16.0
hooks_riverpod: ^0.14.0
freezed_annotation: ^0.14.0
- dev_dependencies
build_runner: ^2.0.6
freezed: ^0.14.0
json_serializable: ^4.1.0
libの中身
counter_controller.dart
counter_controller_provider.dart
counter_state.dart
-
counter_state.freezed.dart
(自動生成) -
counter_state.g.dart
(自動生成) main.dart
解説
自分なりの理解で解説してきます。
イメージ図
雑な図ですが、なんとなく雰囲気をわかってもらえれば良いです。
counter_state.dart
では、count,count10という変数を定義しています。
abstract class CounterState with _$CounterState {
const factory CounterState({
(0) int count,
(0) int count10,
}) = _CounterState;
factory CounterState.fromJson(Map<String, dynamic> json) =>
_$CounterStateFromJson(json);
}
counter_controller.dart
はincrementというメソッドがあり、こいつを呼び出すたびに先ほど定義したcount,count10という変数を+1、+10していきます。
class CounterStateController extends StateNotifier<CounterState> {
CounterStateController() : super(const CounterState());
increment() => state = state.copyWith(
count: state.count + 1,
count10: state.count10 + 10,
);
}
counter_controller_provider.dart
はCounterStateクラスとCounterStateControllerクラスを運ぶ役割をしています。
final counterStateControllerProvider =
StateNotifierProvider<CounterStateController, CounterState>(
(ref) => CounterStateController());
main.dart
はメイン画面のWidgetたちです。ここまで理解できていたらもう大丈夫です。useProviderでproviderを括ってあげることで状態を監視できます。
void main() {
//ProviderScopeで包んであげることによっって、状態を監視できるようになる
runApp(ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends HookWidget {
Widget build(BuildContext context) {
//controllerはCounterStateController型なので、increment()を実行できる。
final controller = useProvider(counterStateControllerProvider.notifier);
//dataはCounterState型であり、count、count10を監視している。
final data =
useProvider(counterStateControllerProvider.select((value) => value));
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'ボタンを押すと数字が増えていくよ',
),
Text('数字が1ずつ増える : ${data.count}'),
Text('数字が10ずつ増える : ${data.count10}'),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
//CounterStateControllerのincrement()を実行している。
controller.increment();
},
tooltip: 'Increment',
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
苦労したところ
counter_state.freezed.dartのエラーが消えない
原因はcounter_state.g.dart
が自動生成されていないことでした。ターミナルでflutter pub run build_runner build --delete-conflicting-outputs
を叩いてやれば自動生成されるはずですが、part 'counter_state.g.dart';
を必要なことを知らずにずっと悩んでました。
import 'package:freezed_annotation/freezed_annotation.dart';
part 'counter_state.freezed.dart';
part 'counter_state.g.dart'; //<--この行が必要
abstract class CounterState with _$CounterState {
const factory CounterState({
(0) int count,
(0) int count10,
}) = _CounterState;
factory CounterState.fromJson(Map<String, dynamic> json) =>
_$CounterStateFromJson(json);
}
まとめ
カウンターアプリではRiverpod+Flutter Hooks+freezedの良さをすごく体感できるわけではありませんでしたが、初めて実装するにはちょうど良かったです。
参考にした記事
Discussion