♨️

Riverpod+Flutter Hooks+freezedで、カウンターアプリを作ってみた

2021/07/29に公開

はじめに

僕は今までProvider + ChangeNotifierをメインとして使っていましたが、どうやらRiverpod + Flutter Hooks + freezedがモダンな書き方らしいので、理解を深めるためにカウンターアプリを作ってみました。

コードはGithubにあげています。
https://github.com/MatsumaruTsuyoshi/counter_freezed_state/tree/master/lib

初期画面 一回ボタンを押すと

構成

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という変数を定義しています。

counter_state.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);
}

counter_controller.dartはincrementというメソッドがあり、こいつを呼び出すたびに先ほど定義したcount,count10という変数を+1、+10していきます。

counter_controller.dart
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クラスを運ぶ役割をしています。

counter_controller_provider.dart
final counterStateControllerProvider =
    StateNotifierProvider<CounterStateController, CounterState>(
        (ref) => CounterStateController());

main.dartはメイン画面のWidgetたちです。ここまで理解できていたらもう大丈夫です。useProviderでproviderを括ってあげることで状態を監視できます。

main.dart
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';を必要なことを知らずにずっと悩んでました。

counter_state.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の良さをすごく体感できるわけではありませんでしたが、初めて実装するにはちょうど良かったです。

参考にした記事

https://qiita.com/karamage/items/4b1aff984b1af7541b73
https://dev.classmethod.jp/articles/flutter_freezed_introduction/
https://medium.com/flutter-jp/state-1daa7fd66b94

Discussion