ログ簿をネイティブAndroidからFlutterへ移行した話
「ログ簿」はもともと Kotlin で書かれたネイティブ Android アプリでした。iOS 版も出したい、けれど一人で二つのネイティブコードベースを保守し続けるのは現実的ではない——そこで Flutter への移行に踏み切りました。本記事では、移行の全体像と設計判断、そして途中で踏んだ落とし穴を残しておきます。
なぜ移行したのか
- iOS/Android の二重保守を一本化したい。 個人開発で使える時間は限られており、UI とロジックを共通化できる効果は大きい。
- UI をゼロから作り直す良い機会だった。 ネイティブ側はすでに Jetpack Compose 化を終えていて、宣言的 UI への移行は済んでいた。Flutter の宣言的 UI とも発想が近く、移植のイメージが付きやすかった。
アーキテクチャの方針
移行にあたって、Clean Architecture + Riverpod を採用しました。レイヤーは次のように分離しています。
- UI 層 … 画面 Widget と Notifier(Riverpod)
- Domain 層 … ドメインモデルと Repository インターフェース
- Data 層 … Repository 実装、ローカル DB、API クライアント
依存の向きは常に「UI → Notifier → Repository(インターフェース)← 実装」に固定し、UI がデータの詳細を知らないようにしています。DB には ObjectBox、状態管理は Riverpod、ネットワークは Dio + Retrofit という構成に落ち着きました。
移行で踏んだ落とし穴
移行は「UI を書き直せば終わり」ではなく、プラットフォーム/ライブラリ境界でいくつも足を取られました。代表的なものを挙げます。
1. データ移行(旧Android DB → ObjectBox)
既存ユーザーの記録を失わないため、ネイティブ Android 時代のローカルデータを Flutter 版の ObjectBox へ引き継ぐ必要がありました。ポイントは DB を開く前(openStore() の前)に移行処理を走らせること。順序を誤ると、空のストアが先に確定してしまい移行できません。
2. メジャーバージョン移行の連鎖
移行の途中で Riverpod・ObjectBox・build_runner のメジャーアップデートが重なり、生成コードとランタイムの噛み合わせで詰まりました。コード生成系はバージョンを合わせて一度に上げる方が、部分的に上げるより結果的に安全でした。
3. ネイティブライブラリのロード問題
一部端末で起動時にネイティブライブラリのロードに失敗するクラッシュに遭遇しました。ビルド設定(ネイティブライブラリの同梱・展開方法)に依存する環境固有の問題で、再現条件の切り分けに時間を使いました。この手の問題は「手元で再現しない」ことが多く、ログと端末条件を突き合わせて地道に絞り込むしかありません。
移行してよかったこと
- iOS/Android のUI・ロジックが一本化され、機能追加のコストが体感で大きく下がった。
- Clean Architecture で層を切ったことで、テスト(Widget テスト・ユニットテスト)が書きやすくなった。
まとめ
ネイティブ → Flutter の移行は、UI の書き換えよりも データ移行・ライブラリ境界・ビルド設定といった「地味だが事故りやすい部分」にこそ時間がかかります。逆に言えば、そこを設計段階で潰しておけば、移行後の開発体験は大きく改善します。
なお、ローカルDBに ObjectBox を選んだ理由 は別記事にまとめました。個別のハマりどころ(DB 移行・ネイティブライブラリのクラッシュ)も順次掘り下げていきます。