概要
Flutter の状態管理パターンの一つである hooks_riverpod + StateNotifier + freezed を使いカウンターアプリを作成する。
環境
- Flutter 2.8.1
- hooks_riverpod 1.0.3
- state_notifier 0.7.2
- freezed 1.1.1
- freezed_annotation 1.1.0
- build_runner 2.1.7
設計
ファイル(責務)分割
今回は以下のように MVVM を意識して責務を分けた。
- lib- main.dart : main メソッドのみ
- app.dart : build メソッドのみ
- widget.dart : View 部分 MyHomePage Widget
- provider.dart : ViewModel 部分 Riverpod Provider
- coutner_state.dart : Model 部分カウンター state
 
hooks_riverpod / sate_notifier / freezed
以前まとめた。
チュートリアル実践
準備
プロジェクト作成。
flutter create tutorial_hooks_freezed cd tutorial_hooks_freezed
必要なパッケージを導入( hooks_riverpod を入れると state_notifier も自動で入るが明示的に)。
flutter pub add hooks_riverpod flutter pub add state_notifier flutter pub add freezed_annotation flutter pub add --dev build_runner flutter pub add --dev freezed
pubspec.yaml は以下のようになる。
dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.2
  hooks_riverpod: ^1.0.3
  state_notifier: ^0.7.2+1
  freezed_annotation: ^1.1.0
dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^1.0.0
  build_runner: ^2.1.7
  freezed: ^1.1.1
責務ごとにファイル分割
main.dart
アプリ実行のための main() のみを記述。
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'app.dart';
void main() {
  runApp(
    const ProviderScope(
      child: App(),
    ),
  );
}
app.dart
MaterialApp を宣言する build メソッドのみを記述。
import 'package:flutter/material.dart';
import 'widget.dart';
class App extends StatelessWidget {
  const App({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyHomePage(),
    );
  }
}
widget.dart
View 部分で画面表示を責務とする。
そのため floactingActionButton は分割するべきかも。
特徴をいくつか抜粋すると
- HookConsumerWidget による hooks_riverpod を利用可能に
- counterProvider の state の監視
- 表示するカウント数 ${count.count}
- floatingActionButton クリック時に increment する ref.read(counterProvider.notifier).increment();
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'provider.dart';
class MyHomePage extends HookConsumerWidget {
  const MyHomePage({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final counter = ref.watch(counterProvider);
    return Scaffold(
      appBar: AppBar(
        title: const Text('Riverpod counter example'),
      ),
      body: Center(
        child: Text(
          '${counter.count}',
          style: Theme.of(context).textTheme.headline4,
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          ref.read(counterProvider.notifier).increment();
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}
provider.dart
ViewModel として Model (state) を操作するため橋渡しを責務とする。
- CounterNotifierをインスタンスに持つ- counterProviderをグローバルで宣言
- incrementメソッド内で- copyWithによる- stateの複製、状態の更新
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'counter_state.dart';
final counterProvider = StateNotifierProvider<CounterNotifier, CounterState>(
  (ref) => CounterNotifier(const CounterState()),
);
class CounterNotifier extends StateNotifier<CounterState> {
  CounterNotifier(CounterState state) : super(state);
  // state は immutable なため、copyWith で複製する
  void increment() {
    state = state.copyWith(count: state.count + 1);
  }
}
counter_state.dart
状態 ( state ) を保持することを責務とする。
- Immutable な CounterState を宣言
- NullSafety 対応のため デフォルト値を宣言
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:flutter/foundation.dart';
part 'counter_state.freezed.dart';
@freezed
class CounterState with _$CounterState {
  const factory CounterState({
    //NullSafetyのためデフォルト値の指定が必須
    @Default(0) int count,
  }) = _Counter;
}
実行
ビルド
flutter pub run build_runner build --delete-conflicting-outputs
起動
flutter run
いつものカウンターアプリが起動したら OK 。

まとめ
カウンターアプリで freezed を使う意味はまったくないが、ハローワールド的な立ち位置。
コードは Github にアップ。
GitHub - runble1/flutter_riverpod_statenotifier_freezed
Contribute to runble1/flutter_riverpod_statenotifier_freezed development by creating an account on GitHub.
参考

【Flutter】Statefulなカウンターアプリをhooks_riverpod+state_notifier+freezedでリファクタリングする - Qiita
FlutterのNullSafetyがstableになってしばらくたち、最近になってriverpodがstableになったので、記念にhooks_riverpod + state_notifier …

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