Flutter開発メモ
Flutterアプリ開発を始め,新しく学んだ知識や取り入れた仕組みを書き綴ります。
開発環境と本番環境を分ける
Dart-define-from-fileを用いて開発環境と本番環境を分ける
Freezedという,イミュータブルクラスを作るためのライブラリ
最も多いユースケースは,Provider/StateNotifierやRiverpodなどの状態管理ライブラリと組み合わせて使うこと。その他,DDDにおける値オブジェクトの実装に用いることもある。
ドメイン駆動設計を取り入れたFlutter開発
テスト方法
単体テスト (Unit Test), ウィジェットテスト (Widget Test), 結合テスト (Integration Test)
Provider + StateNotifier + Freezed な MVVMパターン
時間に紐付いたタスクをCloud Functionsで実行する
FirebaseのCloud Functionsを用いて、時間に紐付いた不定期実行のタスクを処理する方法を調べた。
(時間に紐付いた不定期実行のタスク = ある操作の48時間後にPush通知を送信する、募集の締切日時になったら募集終了処理を行う、など)
Cloud Tasks
Cloud Scheduler 一分おきにタスク探索&実行を行う例。ゴリ押し感が少々MVVMアーキテクチャについて
FlutterではMVVMアーキテクチャが推奨されているが,どうにもこれがReactのHooks + 関数コンポーネントの書き味と全然違ってみえて,難しい。
難しさの言語化
Reactではpropsを用いた値の受け渡しが基本なので,コンポーネントを小さく区切りやすく,個別の実装・テストも容易である。
一方で私の観測範囲では,MVVMアーキテクチャを採用したFlutterのコードは,Viewの最小単位がページ単位になり,そのページ毎に状態管理用のViewModelを用いていることが多い。これ自体が直ちに問題となるわけではないが,Viewが全く区切られずに巨大なウィジェットになるから難しくみえるのである。
巨大なウィジェットができる原因
ではなぜMVVMのFlutterのViewは巨大化するのか?それは「ViewModelによるグローバルな状態管理に頼りすぎるから」だと私は思う。
ページの状態管理やビジネスロジックをすべてViewModelに委譲すると,Viewを小分けにしようにも相互依存を抱えることになり,うまくいかないのだ。
対処法
私なりに考えた対処法は次の通りである。
- 小さなコンポーネント単位では,値の受け渡しにコンストラクタ注入を利用し,さらに状態管理と直接関係のないビジネスロジックは,そのコンポーネントクラスに委譲する。
- 一方で全体としては,グローバルな状態管理には依然ViewModelを用い,ページ構築のViewはあくまでコンポーネントを組み立てるだけの調停役としての役割にとどめる。
まず,何でもかんでもViewModelのグローバルな状態を直接取りに行くことをやめ,コンストラクタ引数で渡すことにする。すると羃等でテストしやすい小さなコンポーネントに分けることができる。
一方で全体としては,コンポーネントを組み立てる「調停役」としてのViewが,適切にViewModelの状態を利用することで,変更時の再描画等を可能にする
MVVMを理解するのに,Flutterのコードだけ見ていても仕方がない。Reactのほうが絶対自分にとってはわかりやすいな…
RiverpodのProviderを利用する方法
Riverpodの基本的な使い方
Providerのオーバーライドなど,重要だが案外知らなかったことも書いてある
Providerがあると何が嬉しいのか,サンプルコードで解説した記事
アイコンを探す方法
'package:flutter/material.dart'
にはデフォルトでIcons
というクラスがあり,そのアイコンをWEB上で探すことができる。ほかにも,FlutterIcon.comというサイトがあるみたい。
Providerの値を動的に(ランタイムに)設定する
追記:これならもっとスマートに管理できるかも?
Riverpodのfamilyについて
familyをつけると,外部から引数を与えることができる。
それだけでなく,その引数は同時にインスタンスを管理するためのIDともなるのである。
Parameter restrictions
For families to work correctly, it is critical for the parameter passed to a provider to have a consistent hashCode and ==.
Ideally, the parameter should either be a primitive (bool/int/double/String), a constant (providers), or an immutable object that overrides == and hashCode.
また,引数として与えられるのは一個のみだが,上述のParameter restrictionsを満たしていれば,タプルでもFreezedで作られたオブジェクトでもいくつでも渡すことができる。
追記: わかりやすい記事を発見
追記2: こんな記事をみつけたけど,通常版のproviderも用意してoverrideする意図は何なのだろうか? わざわざoverrideをしなくても,遷移先のView側で「引数を渡してViewModelを取得する」という処理を書けば十分なのでは?
→MVVMにおいては,Viewに直接コンストラクタ引数で値を渡すのが嫌だから,では?状態管理はViewModelの責務なのに,状態に関連したデータをまずViewに渡すのが嫌,みたいな。
でも一方で,ページ遷移時に遷移先のViewModelの中身を知る必要がある,というのにも違和感がある(これは自分の考え)。ページ遷移時には遷移先のViewだけを意識して,値を渡す必要があればその際に渡せばよく,引数を渡してViewModelを取得する作業はView内部の最初のほうでササッとやってもらえれば十分だと思う。
ViewModel (ステート・ビジネスロジック管理) とView (見た目) の分離をより重視したいのであれば,前者のoverrideを使用したパターンが向いていて,より直感的で宣言的なページ遷移を重視したいのであれば,後者のView内部で引数ありのViewModelを作るパターンが向いている。ということではなかろうか?
ちなみに,familyという道具について知る前にはこういう強引な作り方をしていた
// このmaxTransportationExpensesProviderを呼び出し元でoverrideして,与えたい値を返すようにする
final maxTransportationExpensesProvider = StateProvider<int>((ref) {
// throw Exceptionをしてはならない。
// あとから結局オーバーライドするとしても,一度この中身は読まれる(変数の初期化は不可欠なので。)
return -1;
});
final studentTaskApplyProvider = StateNotifierProvider.autoDispose<
StudentTaskApplyViewModel, StudentTaskApplyState>((ref) {
final maxTransportationExpenses =
ref.watch(maxTransportationExpensesProvider);
return StudentTaskApplyViewModel(ref, maxTransportationExpenses);
});
firebase_auth
がWebビューのビルドに失敗する
Firebase Authを使って普段Android, iOS用に作っているアプリをWebビルドしようとしたら,エラーが出た。
Launching lib/main.dart on Chrome in debug mode...
../../../.pub-cache/hosted/pub.dev/firebase_auth_web-5.5.1/lib/firebase_auth_web
.dart:94:36: Error: Too many positional arguments: 1 allowed, but 2 found.
Try removing the extra positional arguments.
FirebaseCoreWeb.registerService('auth', (firebaseApp) async {
^
../../../.pub-cache/hosted/pub.dev/firebase_core_web-2.6.0/lib/src/firebase_core
_web.dart:43:15: Context: Found this candidate, but the arguments don't match.
static void registerService(
結局,使っているバージョンが古いだけだった