[Flutter] hooks_riverpod + StateNotifier + freezed でカウンターアプリチュートリアル

概要

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で、カウンターアプリを作ってみた

コメント

タイトルとURLをコピーしました