⚙️

Add-to-appの戦い方(2025)

に公開

筆者のAdd-to-appに対するスタンス

筆者は2022年のDroidKaigiにて「Add-to-appの戦い方」というタイトルで発表しました。

https://speakerdeck.com/d_r_1009/add-to-appnozhan-ifang

App-to-appに対するスタンスは、当時から変わっていません。特に次の3点は、今でも重要だと考えています。

  • Add-to-appをどのように終えるのか
  • Add-to-appに期待するメリットは何か
  • Add-to-appによるコスト増は覚悟できるか

筆者はAdd-to-appを長期的に採用するべきでない技術だと考えています。Flutterを全面的に採用するか、採用を取りやめるか、迷いながら手を動かす過渡期の手段という位置付けです。

Add-to-appを取り巻く環境は、2025年現在でも大きく変わっていません。むしろFlutterの採用事例が広く共有されるようになりました。Flutterを採用した場合にどうなるのか、という知見を手探りで集める必要はなくなり、検討の難度は下がってきたと感じています。またFlutterの[1]問題もハッキリ見えるようになりました。Flutterを採用する判断、逆に採用しない判断はしやすくなっています。

事前の検討が容易になったため、Add-to-appをどう適切に運用するかが、重要なテーマになってきました。このため、筆者が考えるAdd-to-appで発生しがちな課題と、その対応策をまとめます。

Add-to-appで発生しがちな課題

Add-to-appを採用すると、次のような問題に[2]直面します。

  • Flutterエンジンの事前キャッシュをコントロールしなければならない
  • ネイティブアプリとリソースを共有しなければならない
  • Flutterとネイティブアプリの間で各種バージョンを揃えなければならない

Flutterエンジンの事前キャッシュをコントロールする

Add-to-appには、Flutterエンジンの事前キャッシュとをする方式としない方式があります。ただ、ユーザー体験を考えるとしない方式は選択肢に入らないでしょう。[3]

https://docs.flutter.dev/add-to-app/performance

https://docs.flutter.dev/add-to-app/android/add-flutter-screen#step-3-optional-use-a-cached-flutterengine

https://docs.flutter.dev/add-to-app/ios/add-flutter-screen#create-a-flutterengine

キャッシュを作成することで起きる問題は、Add-to-app内で表示するWidgetのライフサイクルが、Add-to-appを利用していない時のものと異なることです。キャッシュを保持することは、Add-to-appの表示状態に関わらず、インスタンスが保持されることを意味します。結果としてWidgetの表示と破棄のタイミングが、ディスプレイ上に表示されるタイミングと異なることになります。
そしてAdd-to-appで複数のPage、つまり複数のScaffoldを状況に応じて切り替えたい場合、適切に画面遷移をさせなければなりません。これらを解決する工夫が必要です。

筆者はRouter APIが有効だと考えています。Router APIを利用し、表示されている画面を"宣言的に"切り替えることで、Add-to-appで表示するPageやWidgetのライフサイクルをコントロールできます。Flutterエンジン上で表示しているPageをpathにマッピングすることで、1つのFlutterエンジンの状態を適切にコントロールできるようにする、と言い換えてもいいかもしれません。

コードを示すのも難しいので、やりたいことを示すと次のようなステップになります。

  1. Add-to-appで表示する"アプリケーション"にRouter API(例えばgo_router)を組み込む
  2. Router APIを利用して、Add-to-app内で表示するPageをpathにマッピングする
  3. Method Channelを実装し、ネイティブアプリからAdd-to-app内のpathを変更できるようにする
  4. ネイティブアプリからAdd-to-app Viewに遷移する処理を実装する
    • 先述したMethod Channelを利用し、Add-to-appのpathを更新する
    • Add-to-app内で画面遷移が完了するまで待機する
    • ネイティブアプリ内で画面遷移する
  5. (option)Add-to-app Viewからネイティブアプリに戻る処理を実装する
    • Method Channelを利用し、Add-to-appからネイティブアプリに「戻る」ことを通知する
    • ネイティブアプリ内で画面遷移する
    • (option)Method Channelを通じ、Add-to-app内のpathを初期状態に更新する

optionと書いた箇所は、ケースによっては過剰な実装です。『表示される時に必ずinitStateが実行されるようにしたい』という要件がある場合に、検討してみてください。PopScopeを利用して画面を閉じる際にcontext.go('/')を実行するなど、Add-to-appの中の実装で完結させることもできるはずです。

Method Channelでネイティブアプリとの共存問題を解決する

Add-to-appを利用する場合、Method Channelの理解が必須だと筆者は考えています。これは主に2つの理由からです。

  1. Add-to-appとネイティブアプリ間で通信したい場合、Method Channelを利用する必要がある
    • ネイティブアプリがAdd-to-appキャッシュの状態を更新したいケース
    • Add-to-appからネイティブアプリのAPIリクエスト処理を呼び出したいケース
  2. Add-to-appからFlutterアプリに移行する場合、マイグレーション処理を実装する必要がある
    • ネイティブ側のpreferences情報をFlutter側に引き継ぎたいケース
    • Flutter側からネイティブ側の情報を削除したいケース

筆者のスタンスを改めて強調することになりますが、Add-to-appは過渡期の手段です。全面的にFlutterに移行するか、もしくはFlutterから撤退するかの判断かが、どこかのタイミングで生じると考えています。このため、Add-to-app単体で見ると多少非効率的であっても、後ほどマイグレーションしやすいよう実装することが重要です。

Method Channelを活用することで、マイグレーションしやすい状況を保つことができます。

認証トークンが必要なAPIリクエストを例に挙げます。認証トークンが絡むだけで、ログインやログアウト状態をAdd-to-appにどのように通知するか、どのように認証トークンを保持するかを検討しなければなりません。しかしネイティブ側でAPIリクエストを実装し、それをMethod Channel経由で呼び出すようにすると、この論点を回避できます。Add-to-app側に揮発しない状態を持たせない、この選択肢を選べるようになります。


短期的に見れば、Add-to-app側で共通する処理を実装する方が開発コストは下がります。実装するコードも少なくて済むため、不具合が発生する可能性も低くなるでしょう。一方で、ネイティブアプリの"状態"と関連する処理をAdd-to-appで実装すると、同期する必要が生じます。AndroidとAdd-to-app、そしてiOSとAdd-to-appの2パターンをカバーする必要があるため、よりややこしい問題になりがちです。

事前に『Add-to-appで何を実現したいのか』を明らかにしておくべきだ、と筆者が考える理由はここにあります。
Method Channelを利用すればするほど、Add-to-appで実現できる"コスト削減"効果は下がります。AndroidとiOS、そしてFlutterの3箇所で実装するため、むしろ開発コストが増えているかもしれません。一方でAdd-to-appを解消する際に発生する課題を、Method Channelを利用することで回避できる可能性があります。

Add-to-appとネイティブアプリ間で各種バージョンを揃える

Add-to-appで作成したコードは、モジュールになってネイティブアプリに組み込まれます。このため、Add-to-app内で利用するライブラリのバージョンは、ネイティブアプリと揃える必要があります。Firebaseなどはわかりやすいのですが、例えばimage_cropperのような『Flutterからライブラリを呼び出す』パターンのものは、見落としがちです。

そのほかには、Kotlinのバージョンが問題になることがあります。module内の.andorid/ディレクトリは、aarを作成するためのAndroidプロジェクトファイルです。厄介なのは、このディレクトリが自動生成されるという点です。プロジェクトファイルにはKotlinのバージョンを指定するコードも含まれるため、大胆な工夫をしない場合には、Flutter SDKが規定するKotlinのバージョンがビルド時に適用されます。[4]

なお記事執筆時点の最新Flutter SDKは3.29.2ですが、Kotlinのバージョンは1.8.10です。最新のバージョンが2.1.20であることを踏まえると、足並みを揃えるのに苦労するかもしれません。

https://github.com/flutter/flutter/blob/3.29.2/dev/tools/bin/generate_gradle_lockfiles.dart#L291


これらの問題は、どうしても"気合い"で解決するしかありません。

Method Channelでネイティブ側に処理を実装することで、Add-to-app側に含めるライブラリを減らすことはできますが、これはまた先述のような議論を引き起こします。各所に気を配り、問題が生じないようにアップデートを行いましょう。

まとめ

Flutterをアプリに段階的に導入したり、チームがFlutterを学びつつ開発を進めるのに適した技術です。一方で、重ねての主張になりますが、期待値をあらかじめ調整しておかなけれならない技術でもあります。

脚注
  1. 特に取り巻く環境に関する ↩︎

  2. おそらく一般的に ↩︎

  3. 特定ユーザーのみにAdd-to-appで開発したUIを提供する場合などに、事前キャッシュをしない方式を選択することはあるかもしれません ↩︎

  4. 独自にバージョンを指定してaarを生成し、それをgit管理するといった方法もあります ↩︎

GitHubで編集を提案

Discussion