Flutter化の振り返り
加賀ゆびぬきシミュレータアプリをFlutter 化したときの内部に関わる記録を残したかったので、あらためて記憶を掘り起こしました。
理由
Android、iOSの2環境に、同じロジックで提供していたため、不具合修正も2箇所に実装していて手間がかかっていました。ほぼ1人プロジェクトなので、メンテにかかる時間を減らす価値があります。 宣言的UIに慣れるにも良い機会です。
方針
- UI、ロジック共に、OSによる分岐は極力しない
- データ管理もプラットフォーム間で共通化する
- 保存単位とファイル単位が一対一なのは共通だが、保存ファイル名は違うルールだった
実装の記録
中心機能である、刺し図の描画から着手しました。夫が手伝ってくれたので、一番複雑な手順の並び替えを実装してもらいました。 画面パーツを作っていき、データを指定すれば、各画面のプレビューができる状態にしました。
その後、Providerを使ったデータ管理を導入していきます。ゆびぬきの編集は3つの画面で構成されており、ひとつのオブジェクトを更新していくため、Providerで状態を同期できるようにしました。Provider経由で取得したデータを、各画面でViewModelに変換して表示します。
画面遷移は Navigator 2.0 を使いました。パッケージは使わずに、画面Stackを自前で構成しています。当時は、解説記事も少なく、Flutter の Navigator 2.0 の解説 前編とコードを読みながら進めていました。現在だと、便利なパッケージもあり、ある程度隠蔽して使うこともできるようです。
過去のバージョンのアプリデータをマイグレーションする機能は、アプリ内の深い階層にインポートログも見れるようにしました。
開発終盤に手順管理のUIに手を入れて、初心者にわかりづらい「グループ」を消す大きな変更をしました。「グループ」はデータ都合の集合なので、実際にゆびぬきを作る上では対応する集合を表す名前がありません。それ故に、よくわからない概念が登場し、「初心者には意味がわからないもの」になっていました。
アーキテクチャ
最終的に、MVVM風 + Navigator 2.0 でできています。(図は拡大推奨)
データからModel上で、Viewで表示する項目をViewModelに変換し、Viewで表示しています。「風」をつけたのは、ViewModelには振舞いを持たせず、画面特化のEntityとして扱っているためです。
このあたりは、SwiftのThe Component Architecture を学ぶとよりよい方法が見えそうな気がしています。
リリースから1年
状態管理の失敗
Navigation Stackをアプリで厳密に管理しているため、たまにStack操作の異常を検知しています。StateErrorを仕込んであるので、Crashlyticsに届きます。ドキュメントを二重にひらこうとしたり、手順の詳細を二重に開こうとするなどのケースで起きています。連打が可能になっていそうで、対策を入れる予定です。Flutter故に、描画が遅いことで起きている可能性もあります。単純に連打だとすると、操作画面側でローディングを入れるなども有効かもしれません。
assertionに変更して、検知はさせつつ、本番ではクラッシュしないようにする必要もあります。
エラー監視
実装方法にもよりますが、エラーをCrashlyticsに送るようにしていても、non fatal error なので監視に注意が必要です。 Document に従って、推奨されている記法を使いましたが、見直します。
runZonedGuarded<Future<void>>(() async { runApp(const YubinukiApp()); }, (error, stack) => FirebaseCrashlytics.instance.recordError(error, stack));
Warningとして記録する実装を書いたのと区別できないのが困っています。
これから
- 一部のパッケージ対応の都合で nullable のままのコードを読みやすく変えられそうです。パッケージの Non-null 対応も進んでいることでしょう。
- 前述のエラーまわりは改善して監視を楽にしたいです。
- 半年に1回くらいはFlutter の更新もしたいものです。