最も簡単なカウントアプリで Riverpod ・ flutter_hooks ・ Freezed の使い方を学んでみた
最近、Flutter界隈では、RiverpodとFlutter Hooksが話題ということで記事にしておきます。
flutterを学び始めてまだ日は浅いので、もっと良い実装の仕方があると思いますが、自分の学習にもなればと今回デフォルトで用意されているカウントアプリをStatefulからRiverpodを使用したコードでの実装に変更していきます。
「もっとこうした方が良いよ」というご指摘などあれば、嬉しいです!
まず初めにRiverpodとは何か全くわからない人のために、簡単に言えば状態管理をより扱いやすくするためのパッケージです。
状態管理とはなにか?
初心者がFlutterで開発する上で初めの方にまず疑問に思うことが「Stateless? Statefull? 何それ?」と思うはずです。(自分が経験しました)
ざっくりいうと、Statelessとは状態を持ちません。UIに固定で表示されるものです。
Statefullとは反対に状態を持ちます。
じゃあ状態を持つって何? って初心者の方はなりますよね。それは、、、
データを元にUIを作る仕組みのことを状態を持つと呼びます。
その状態を扱いやすく管理することを状態管理といいます!
では本題に移ります。
今回アーキテクチャをMVVMパターンで設計しています。
まずパッケージのインストールを行います。
dependencies:
flutter:
sdk: flutter
flutter_hooks: <任意のバージョン>
hooks_riverpod: <任意のバージョン>
freezed_annotation: <任意のバージョン>
dev_dependencies:
flutter_test:
sdk: flutter
build_runner:
freezed: <任意のバージョン>
json_serializable: <任意のバージョン>
次にデータクラスをFreezedを使い、作成します
import 'package:freezed_annotation/freezed_annotation.dart';
part 'count_data.freezed.dart';
part 'count_data.g.dart';
@freezed
class CountData with _$CountData {
const factory CountData({
required int count, //カウントの総数
required int countUp, //カウントアップ
required int countDown, // カウントダウン
}) = _CountData;
// {} ではなく =>(アロー)を使ってください
factory CountData.fromJson(Map<String, dynamic> json) =>
_$CountDataFromJson(json);
}
単純にカウントを増やすだけでなく、カウントを減らすボタンやリセットするボタンを実装しています。
次に、ViewとModelのやりとりの中間に位置するView_Modelの実装を行います。
view_modelの役割を果たす部分をcount_data_controller.dartというファイルに記述していきます。
先程定義したデータクラスを元にStateNotifierを使用し、CountDataControllerというクラスを作成します。
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hooks_riverpod_freezed_countapp/model/count_data.dart';
class CountDataController extends StateNotifier<CountData> {
CountDataController(this._reader)
: super(CountData(
count: 0,
countUp: 0,
countDown: 0,
)); //初期値を全て0に設定しています
final Reader _reader;
//カウントの総数とカウントアップの回数を1増やすメソッド
void increment() {
state = state.copyWith(
count: state.count + 1,
countUp: state.countUp + 1,
);
}
//カウントの総数を1減らしカウントダウンの回数を1増やすメソッド
void decrement() {
state = state.copyWith(
count: state.count - 1,
countDown: state.countDown + 1,
);
}
//全てのカウントをリセットするメソッド
void reset() {
state = CountData(count: 0, countUp: 0, countDown: 0);
}
}
viewModelのクラスが作成できたら、次にProviderを記述していきます。
別でファイルを作成しても良いのですが、今回はCountDataControllerクラスがあるファイルに書いています!
StateNofitierProviderを使用して、countDataProviderを作成します。
1個目の型パラメータにはStateNotifierの型を、2個目の型パラメータにはStateNotifierが保持する型を持たせる必要があります。
stateの値が変わると自動的に変更の通知をしてくれます!
notifyListeners()をわざわざ書かなくて済むので書き忘れがなくなり便利です!
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hooks_riverpod_freezed_countapp/model/count_data.dart';
final countDataProvider = StateNotifierProvider<CountDataController, CountData>(
(ref) => CountDataController(ref.read),
);
<省略>
これでModelとViewModelは以上です!
あとはUIに位置するViewの部分の実装をして完成になります!
以前は、 HookWidget と useProvider の組み合わせにより、各Providerを読み取ることができていましたが、Riverpod v1.0.0からはHookConsumerWidgetとref.watch(read)を使用して書いていきます。
HookConsumerWidgetを継承することで、buildメソッドの引数にWidgetRef refが追加されます。
ref.watch(Provider名)とすることで、そのProviderにアクセスすることができます。watchを使う場合、値の変更があった時にリビルドされ画面が更新されます。
readを使う場合は、リビルドはされません。(例えば、カウントアプリでプラスボタンを押したときにリビルドされて欲しいのは数字のみで押したボタンのリビルドは必要ないです)
ref.read に渡す Provider は、.notifier を付与する点に注意しましょう。
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hooks_riverpod_freezed_countapp/view_models/controller/count_data_controller.dart';
class MyHomePage extends HookConsumerWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) { //追加
final countState = ref.watch(countDataProvider);
final countNotifier = ref.read(countDataProvider.notifier);
return Scaffold(
appBar: AppBar(
title: Text(
ref.watch(titleProvider),
),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
countState.count.toString(),
style: Theme.of(context).textTheme.headline4,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
FloatingActionButton(
onPressed: () {
countNotifier.decrement();
},
tooltip: 'Increment',
child: const Icon(Icons.horizontal_rule),
),
FloatingActionButton(
onPressed: () {
countNotifier.increment();
},
tooltip: 'Increment',
child: const Icon(Icons.add),
),
],
),
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
countState.countDown.toString()),
countState.countUp.toString()),
],
),
)
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
countNotifier.reset();
},
tooltip: 'Increment',
child: const Icon(Icons.restart_alt),
),
);
}
}
以上で、カウントアプリの実装は完了になります。
今回参考にさせて頂いた記事は以下になります。