特に驚いたり重要だと思うところを太字にしておきます。自分のメモをベースにAI使って直してもらってます。変な所があれば編集リクエストかコメントなどで教えてください ![]()
今回の学びの推しは "Navigating Dependency Injection with Metro" のAsk the speakerです
Building with AI in Kotlin
本セッションは「IDEでのAI支援」と「Kotlinでエージェント型アプリを作る」までを一気通貫で紹介。
-
JetBrainsの支援は“段階”で整理され、それぞれIntelliJで提供されている:
- 補完(ローカル/クラウド、提案の部分受け入れが可能)
- 次の編集提案(Rename連鎖や、IDEのリファクタ提案の活用)
- インライン生成(選択範囲の変換/小実装の生成)
- チャット支援(ファイル文脈を添えた回答をそのまま適用できる)
- Junie(AIコーディングエージェント)
- Vibe coding(高抽象の指示だけで探索的に実装)
-
コミット運用では、JetBrains AIがコミットメッセージ生成のプロンプトを固定化でき、チーム流儀のフォーマットを強制しやすい。
-
Junieは、指示→計画→編集→検証→実行まで自動。既存設計/UIに合わせて複数ファイルへ横断変更し、差分レビュー可能。GitHub連携ではIssueからPRを自動生成する機能がある(EAP)。
-
AIをアプリ側に組み込むレイヤでは、ML Kit/MediaPipeでオンデバイス推論を活用可能(端末制約はあるがネット不要の利点)。モデル接続は各社SDKに加え、OpenRouterの“1つの入口で多数モデルを切替”できる運用が実験に向く。
-
エージェントフレームワークは Koog(読み:クーグ)。Android / iOS / Web / JVMで動作。Koog自体は推論エンジンではなく、ワークフロー記述と統合に特化して軽量。
-
Koog:
- Node(型付きの入出力)でLLM呼び出しやツール実行を表現し、ノードをつないだグラフ(Strategy)で振る舞いを記述。Kotlin DSLにより型安全な設計ができる。
-
AIAgent<String, String>().asTool()で“エージェント自体”を他エージェントからツールとして呼べるため、機能の合成・再利用が容易。 - Compaction(履歴圧縮)で長対話のトークン増大や品質劣化を抑制。
- 運用の勘所は、ツール実行に確認ダイアログ/権限境界を設ける、トークン/時間の計測とログで可観測性を確保、
guidelines.md等で規約を明文化してエージェントの前提に組み込む、の3点。
Ask the speaker
- 既存AIエージェントツールの移行方針:AI周りから段階的に移行するのが良い。
- Koogプロジェクトで新しいKotlinX Datetimeが入っていないので新しいKotlinX Datetimeを使っているとビルドできない件:対応を伝えるとの回答。
Gen AI for Android Developer
- 登壇者:Google 本社 Android チーム。20%プロジェクトで AI と縦書き字幕(text-vertical)に取り組み中
- 目的:Android アプリで生成 AI をどう実装・運用するか。オンデバイス/クラウド双方の選択肢と実装の勘所
-
事例(KakaoPay)
- メッセージに「荷物送りたい」等の自然文 → Gemini に JSON 出力を指示して受取人名・住所・電話を抽出 → 注文を自動生成
- 従来の ML なら大量データ+学習が必要だが、プロンプトと出力フォーマット定義だけで実装可能
- 依頼主と受取人の区別など文脈理解が必要な箇所は生成 AI が有利
-
実装パスは2つ
-
オンデバイス:Gemini Nano(軽量化モデル、量子化などで最適化)
- Gemini Nano v3 が Pixel 10 に搭載
- MatFormer(大モデル内に小サブモデルを入れ、軽い処理は小モデルで捌く最適化)
- ランタイムは Android の「AI Core」(モデル配布・更新・端末別チューニング込み、アプリサイズに影響しにくい/ファインチューニング不可)
-
クラウド:Firebase AI Logic(旧 Vertex AI in Firebase)
- 最新の Gemini Liveや Nano Banana 系等を API で利用(画像生成や長文解析など重い処理向き)
-
-
他ツールの位置づけ
- MediaPipe:最適化は弱めだがオープンモデルの実行に柔軟
- AI Core:端末最適化が厚いがファインチューニング不可
- 「Prompt API」は現在 Experimental(将来より正式な形で公開予定)あまり詳細は出なかったがAsk the speakerの内容的には、普通にAIがAPIっぽく呼び出せるっぽい?
-
モデルの中身をざっくり
- トークナイズ → 埋め込み(ベクトル化)→ デコード(1トークンずつ生成)
- Self-Attention で文脈を加味しながら生成
- 速度最適化:KV キャッシュの工夫、確度の高い部分を先読みする推測生成(Speculative Decoding)など
-
プロンプト設計の基本姿勢
- LLM は「頭は良いが経験が浅い新入社員」。こちらが優しくマイクロマネジメントするイメージで細かく指示
- 否定命令(〜するな)は苦手。やってほしい形に言い換える
- スコア採点(80点か?)よりも分類に落とす(A/B/C いずれか)方が安定
- プロンプトは英語が有利(英語→日本語出力でも OK)
- 提示順序が効く:ロール → 条件 → 例 → 出力形式の順に明示
- ステップバイステップ指定(手順を段階化)で精度が上がりやすい
- 「これは私のキャリアにとって大切です」のような“感情トーン”を加えると、注意が広がり出力が安定するケースあり
-
例示の威力
- 最も効くのは「大量の良い例」を与えること(Few/Many-shot)
- まず汎用プロンプトで実装 → 誤りパターンを収集 → 例として追加 → 繰り返し改善(“準ファインチューニング”的に効く)
- 厳しすぎる一括制約より「分割の手順書+最終整形」の方が改善しやすい(KakaoPay 例でも有効だった)
-
評価とパラメータ
-
評価には F1 スコア(適合率・再現率のバランス)。要約など正解が1つでない場合は専用メトリクスも検討
-
推奨初期値(用途に応じて調整)
- Temperature:0.5(0 は反復・崩れ増/0.2〜0.7で探索)
- Max Tokens:100(速度重視。足りなければ増やす)
- Top-K:40
-
Temperature を 0 にしても幻覚は根治しない(確率1位≠常に正解)。むしろ少しの多様性が要約などでは有利
-
出力フォーマット崩れは「制約付きデコーディング」(JSON など形を固定)で軽減
-
-
今後の話題(触れられた範囲)
- Android の MCP 的なエージェント連携(Google 製アプリをツールとして呼び出す “Magic Suggest” のような体験)
- Android 17 以降、アプリ内の長い処理をエージェント化できる方向性(詳細は次回以降)
Ask the speaker
-
Q. どうやって AI のプロンプトや出力のテスト・測定を回す?
-
A. まずは Python ノートブックで検証するのが一般的。
- ただし評価アルゴリズム(どの指標で良し悪しを判定するか)の設計が難所。
- 実運用ではデータ収集→指標設定(例:F1)→回帰テストの仕組み化が必要。
-
Android端末で実現するオンデバイスLLM 2025
- MediaPipe=LiteRTベースのLLM向け高レベルAPI。NNAPI/GPUデリゲートを使える前提なのでAndroid実装の敷居が低い。
.taskモデルはサイズ大:アプリ同梱ではなくPlay Asset Delivery配信がおすすめ。 - Gemma 3 1B(約554MB)は“速さ・サイズ・品質”のバランスが取りやすい。短文要約/推敲は速く、自由生成は同一出力になりがちなのでプロンプト設計で回避。
- Gemini Nanoは端末限定&実験的(AICore経由、Pixel 9系など)。品質は高い一方、まれに出力ループが起きる観測あり。端末カバレッジを考えると現状はMediaPipe採用が現実的という所感は妥当。
- サンプル:https://github.com/MasayukiSuda/DroidKaigiLocalLLMSample
- (最小補足)比較対象としてLlama.cppも登場:モデル選択肢が広い/日本語特化も選べるがCPU寄りで電池・発熱が増えやすい。軽量実用なら Llama-3.2 3B Instruct(Q4_K_M)など。
Androidエンジニアとしてのキャリア
-
Take Action
- 最初の一歩は「行動」。本や情報だけではなく、小さく試して外からの反応で理解を確かめる。
- 自己認識をハックして行動に結びつける。 自分を“説得”する仕組みを作る(ご褒美・締切・仲間と約束など)。
-
Plan
- 無計画は達成確率が良くない。 ざっくりでも仮説→検証→観測→調整のループを回す。
- 何のプロになりたいかを言語化し、そのために必要なスキル・経験・評価の指標(例:社内評価、コミュニティでの評判)を決める。
- キャリアは価値観の投影。 定期的に(例:2〜3か月)価値観と行動のズレを点検する。
-
ロール選択(IC/EM)
- IC=Individual Contributor(個人として技術で影響する)/EM=Engineering Manager(人とプロセスで影響する)。
- どちらが正しいではなく、価値観と得意で選ぶ。小さく任せを受けて相性を確かめる。
-
スペシャリスト/ジェネラリスト
- スペシャリスト/ジェネラリストの二択ではない。 多くは「広くわかる+どこか深い」。まずはニッチでもよいので自分の専門を持つ。
-
副業の選び方
- 本業で得られない挑戦・スキルを目的に選ぶ。お金だけを目的にすると長期の投資効率は下がりやすい。
- コミュニティ運営など“非金銭の価値”(ネットワーク、マネジメント経験)も大きい。
-
お金のモチベーション
- 動機として正直でOK。ただし高いレベルでは「面白さ・技術挑戦・組織でしかできない仕事」が効きやすい。
-
コミュニティでの発信
- 発信はブランディングになり、良いオファーにつながる。 登壇・ブログ・書籍などは外から見える実績になる。
-
見えない壁を感じたとき
- 会社の待遇レンジの限界か、自分のプロセス不全かを切り分ける。やり切って合わなければ外に道を求める。ただし視野狭窄に注意。
-
フィードバックの扱い
- 曖昧なフィードバックから弱みをモデル化する。感情と事実を分けて言語化し、次の行動に落とす。
-
仲間・メンター
- 上司ひとりに依存しない。複数のメンター/同業の仲間を持ち、コミュニティで再現性のある支援網を作る。
-
変化への追従
- スキルを継続更新。ひとりで難しければ仲間と小さく実践する(新技術の社内LT、週1アウトプットなど)。
Ask the speaker
- Q: 自分を騙すコツは?
- A: 「嫌いなところを直視しないようにする」。食わず嫌いの食べ物を少しずつ食べられるようにする感覚で、避けたいことを直接見ない工夫をしつつ小さく始める。
2日目
Compose Multiplatform × AI で作る、次世代アプリ開発支援ツールの設計と実装
-
スピーカーと前提
- 元 Google Android DevRel(〜2023)。現在は ComposeFlow を開発。
- Compose Multiplatform(CMP) 向けのドラッグ&ドロップでCMPアプリをリアルタイム編集するビジュアルエディタと、その上で動く AI エージェントを紹介。
-
ビジュアルエディタの仕組み
- UIは木構造で管理:各UI要素=Kotlinデータクラス(ノード)。
MutableStateListで子要素や Modifier を保持 → 変更時に再コンポーズして即時反映。 - Modifier は順序が意味を持つため、リストを fold(畳み込み)で合成して最終的な
Modifierを生成(Compose 内部の CombinedModifier と同等の考え方)。 - エディタ上でパディング・サイズ・背景色・並び替えをライブ編集可能。結果はプレビューにそのまま反映。
- UIは木構造で管理:各UI要素=Kotlinデータクラス(ノード)。
-
コード生成
- 各ノードは「自分をComposeコードとしてどう出力するか」も定義(KotlinPoetで
importなども自動生成)。 - プロジェクト出力時はテンプレート+ノード生成コードを合成し、CMPアプリとして実行(エミュレータ/Web プレビュー)。
- すべて抽象化できない箇所は将来的に「カスタムコード挿入」を想定。Kotlin LSP により IntelliJ 外でもエディタ体験を提供可能。
- Kotlin LSP(Language Server for Kotlin)により、IntelliJ の外でも編集体験を提供可能
- 各ノードは「自分をComposeコードとしてどう出力するか」も定義(KotlinPoetで
-
データ永続化
- すべてのデータクラスに
@Serializable。中間表現として YAML に保存(プロジェクト=1ファイル)。
- すべてのデータクラスに
-
AIエージェント連携
- 「AIエージェント=LLM出力でワークを制御するプログラム」。エディタ操作をLLMのツールとして公開し、UI編集を自律化(例:ナビゲーションドロワー追加)。
- 実装:既存エディタAPIに対応する「LLM用メソッド」を別途用意(引数はKotlinクラスではなく YAML 文字列などプリミティブ)。メソッド/引数にアノテーション付与 → アノテーション+KSPでツール定義を自動生成(各ベンダの Function/Tool 形式に合わせる)。LLMが返す YAML をデシリアライズして既存の編集処理を実行。
- YAML を正しく書かせる材料として Kotlin → TypeScript → JSON Schema でLLMがYAMLを書けるようにする仕組みを用意。JetBrains の Koog ならデータクラスから直接スキーマ生成できそう、との言及。
-
エージェントのやり取り
- 「ユーザーリクエスト+現在のアプリ状態」を LLM に渡す → LLM が必要なツールを呼ぶ → 実行後の状態を再度 LLM へ → ツール呼び出しがなくなるまでループ(本質は while ループ)。
-
信頼性・運用の工夫(実装に役立つ具体策)
-
エラー位置つきYAMLフィードバックで自己修正率を向上:YAMLのどこで失敗したかを位置情報つきで返すと、次回ツールコールで自己修正されやすい。
-
「リトライより前進」を選ぶケース:YAMLの値が enum に存在しない場合は初期値にフォールバックして処理続行(後からエディタで直せるためUXが良い)。
-
プロンプトのキャッシュ活用:サイズが大きく変化しにくいスキーマ等を前段に置き、可変情報を後段へ。1リクエスト内の順序設計がコスト最適化に効く。RAGでのスキーマ検索も試したが、このケースでは「プロンプト同梱+キャッシュ」の方が安定。
-
-
デモで見せたこと
- 「SNSアプリを作って」で初期画面群を自動生成 → エディタでサイズ・色など微修正 → 追加指示「ナビゲーションを追加し、アイコンも設定」→ ツールコールでナビゲーションドロワーが組み込まれ、開閉もエディタ上で確認可能。
-
現状の配布形態
- デスクトップアプリを提供中。ブラウザ対応を準備中。
Ask the speaker
-
Q: キャッシュについて詳しく
- A: 多くのAI APIにキャッシュがあり、「変更されない大きな情報(例:スキーマ)」を前方に、可変部分を後方に置くと同一リクエスト内でも効きやすい。順序設計がコスト最適化に重要。
-
Q: エラーメッセージはどう活用?
- A: LLMが誤ったYAMLを返した際に「どの位置が誤りか」を返すと、次のツールコールで正しいYAMLを返す確率が上がる。実装では LocationAwareSerializerWrapper でYAMLのエラー位置を取得し、フィードバックに含めている。
Navigation 2 を 3 に移行する(予定)ためにやったこと
※このメモは途中から聴講しました。またそもそもNav3に付いて理解できてなかったので前半は「自分の調べメモ」、以降は「セッション内容ベース」です。
(ここまでは 自分の追いつくためのメモ ベース)
-
Nav3 のざっくり像
-
entries(BackStack 相当のリスト)を状態として持ち、NavDisplayで可視化する。 - UI の状態=遷移の状態を同一情報として扱うため、イベント発火ではなく“状態を直接更新して遷移”するイメージ(Compose と整合的)。
-
-
最小イメージのコード(学習用)
@Composable fun NavExample() { val backStack = remember { mutableStateListOf<Any>(Home) } NavDisplay( backStack = backStack, onBack = { backStack.removeLastOrNull() }, entryProvider = { key -> when (key) { is Home -> NavEntry(key) { ContentGreen("Welcome to Nav3") { Button(onClick = { backStack.add(Product("123")) }) { Text("Click to navigate") } } } is Product -> NavEntry(key) { ContentBlue("Product ${key.id}") } else -> NavEntry(Unit) { Text("Unknown route") } } } ) } -
Scene
- Scene は entries の“サブリスト”を1画面として束ねる。List/Detail の左右分割などアダプティブなレイアウトに使う。
(ここからはセッション ベース)
-
設計の大枠(Nav2→Nav3)
-
同期ズレの温床だったイベント駆動を避け、UI 状態と遷移状態を統合。
-
Key/Entry/BackStack
- Key は任意型(
Any)。同一 Key は同一 Entry とみなされる(Nav2 は毎回新規)。 - 再利用させたくない時は
contentKeyをユニーク化(例:UUID)。 -
rememberNavBackStackでプロセスキル耐性を得る場合は Key にSerializable制約(UI 外レイヤーへ波及しない設計が必要)。
- Key は任意型(
-
-
Scene / SceneStrategy(アダプティブ)
- Strategy が「どの entries をどの Scene に描くか」を決定。
- List/Detail の 2 ペインは画面サイズでペイン数を切替。
onBack(count)で複数 Entry を一度に戻す遷移も扱える。
-
OverlayScene(Dialog/BottomSheet)
- OverlayScene は他の Scene の上に独立表示(遷移アニメの影響を受けない)。
-
DialogSceneは提供。BottomSheet は Navigator 相当が未提供のため、OverlayScene を拡張して代替。 - Entry として扱えるため BackStack 制御に乗り、Entry 単位の SavedState/ViewModel を持てる。
-
NavEntryDecorator(横断拡張)
- Entry 単位に SavedState/ViewModel/権限制御などを横断適用できる。
- 既定:
sceneSetupNavEntryDecorator(Scene 間移動でもrememberを保持)、savedStateNavEntryDecorator。 -
viewModelStoreNavEntryDecoratorは SavedState デコレーターの後に定義必須(順序違いはクラッシュ)。
-
Hilt 統合
-
hiltViewModel()は Nav3 でも利用可能。 - Nav3 の Key は SavedState に自動保存されないため、必要なら AssistedInject 等で受け渡す。
-
-
ネスト構成
- 専用 API はなく、ネストしたい Entry 内で
NavDisplayをネストして再現。 - 親の Strategy/Decorator は子に自動適用されない(親子で独立設定)。
- 最新 alpha 周辺で「ネスト下
NavDisplayのモーダル操作でクラッシュ」の修正の話があった
- 専用 API はなく、ネストしたい Entry 内で
-
段階的移行の方針
- まずネストグラフから:ネストを「フラットなルート+内部で
NavDisplayネスト」に置換。 - すべて置換後、最後に
NavHost→NavDisplayに切替。
- まずネストグラフから:ネストを「フラットなルート+内部で
-
Deep Link
- 標準の Deep Link 機能はなし → URL を自前で Key 化し BackStack 操作(URL→Key 変換インターフェースを各 Key に実装、最初にマッチしたものを push)。
-
BottomNavigation の state 保持ハック
-
背景:
Home → A → B → Aとタブ切替すると、Bに切り替えたタイミングでAをバックスタックから消す感じにしてしまうと、Nav3 は「スタック外の Entry の状態」を保持しないため A の state が消えてしまう。 -
解:ポップせず順序入替で保持。
- 初期に全タブ Key を積み、末尾が
Homeになるよう並べ替え。 - タブ選択時は
Homeを最後に積み、その後に選択タブ Key を積む ⇒ どのタブでも戻ると必ずHome。 -
Homeで戻る時に他タブが背面に見える問題は、Home用カスタム Scene でpreviousEntriesを空にして「これ以上戻れない」扱いにし、Predictive Back の背面描画も抑止。
- 初期に全タブ Key を積み、末尾が
-
Performance for Conversion! 分散トレーシングでボトルネックを特定せよ
-
「パフォーマンスが悪い=離脱やCV低下」に直結するが、絶対的なしきい値は存在しない。類似アプリやAndroid Vitalsなどの基準を参照しつつ、自プロダクトの期待値で決める。ストアによって基準が異なる例もある。
-
計測する時間は分けて考える。最初の表示まで/ユーザー操作可能(完全表示)まで/画像の読み込み開始→表示完了まで(ダウンロード、デコード、キャッシュIO、変換などの合算)/ネットワークリクエストの各フェーズ(DNS・TCP・TLS 等)。
-
アプリ起動はタイプ(COLD/WARM/HOT)だけでなく「ユーザー操作起因」か「バックグラウンド起因(通知・Broadcast・WorkManager 等)」かを区別して観測する。
-
Firebase Performance の限界(要点)
- 子トレース(親子関係)の設定ができない/トレース開始時刻を任意に指定できない(別プロセス開始の計測が困難)。
- ヒストグラムが作れない。
- BigQuery エクスポートに 24h のラグがあり、当日SLOのモニタリング用途に不向き。
- サンプリングは制御不可。Head based の固定で、Tail based sampling が使えない(エラーや特定属性のトレースを必ず残す、といった運用が難しい)。
-
サンプリングの整理
- Head based:開始時に確率で送るか決める(安いが中身は考慮しない)。
- Tail based sampling:終了時に内容を見て選ぶ(エラー含む/ABテスト関連だけ残す等が可能)。調査効率とコスト最適化に有効だが Firebase Performance では不可。
-
分散トレーシングの利点
- 複数サービス・アプリ内処理を1本のトレースとして可視化。親子関係(因果)で遅さやエラーの位置がわかる。
- 分散トレーシングでモバイルとバックエンドを一本の“因果グラフ”として可視化できるため、「起動全体の中のAPI」「API内のDNS/TLS」などボトルネックの連鎖を一目で追える。
-
OpenTelemetry(OTel)の位置づけ
- CNCFのOSS規格。トレース/メトリクス/ログを共通プロトコルで扱い、SDKも各言語に用意。
- OpenTelemetry Collector を挟むとベンダーロックインを避けつつ複数バックエンドへ同時エクスポート(例:Prometheus、Grafana、DataDog、Honeycomb など)。Collector は Receiver→Processor(バッチ化・サンプリング等)→Exporter の三段構成。LB 併用で単一点障害を回避。
-
Android実装の要点(セッションでのアプローチ)
-
Embrace Kotlin SDK を土台に、アプリ側インターフェースで包む軽量 Catracer SDK を作成。SDK依存の差し替えを容易にし、アプリレベルでスパンを管理。
-
Application.attachBaseContext で“起動開始時刻”を最も早い地点で取得できる。
- 起動元の判定:次フレームに Runnable を投げ、最初の Activity のライフサイクル(onActivityResumed など)発火有無で「ユーザー操作」か「バックグラウンド」かを切り分けて記録。
-
ネットワークは OkHttp の EventListener で DNS/TCP/TLS/送受信 などをスパン化し、URLや端末情報を属性として付与。
-
ログ/メトリクスとトレースIDで相互にひも付け、1画面・1リクエスト内で「何が遅いか」を統合的に確認。
-
-
Collector と可視化
- アプリ→OTel Collector(中継・加工)→各種バックエンドに送信。設定変更だけでマルチエクスポートが可能(Grafana/Honeycomb/DataDog 等で同じトレースを閲覧)。
-
導入後に得られた知見(例)
- 完全表示まで ≈3秒のうち、DNS 解決が約 500ms のボトルネックと特定。因果グラフ上で最遅スパンが明確になり、最適化の打ち手(DNS周り・CDN設定・名前解決キャッシュ等)に直結。
- Firebase Performance でも把握できる指標は多いが、分散トレーシングでは「関連付け」が加わるため、原因追跡の反復が短縮される(手元で再現できないユーザー環境の問題にも有効)。
-
Ask the speaker(というよりはブースにて)
- Collectorって、サーバー用意しないといけないってこと?アプリのエンジニアとしてはちょっと大変じゃない?
- DataDogとか色々に送れるエンドポイントあって、そこに送れるはずで、アプリ側の実装はあんまり変えずに送れるようにできる
- Collectorって、サーバー用意しないといけないってこと?アプリのエンジニアとしてはちょっと大変じゃない?
Navigating Dependency Injection with Metro
-
依存性注入(DI)の導入は、Singleton → Service Locator → Constructorでの注入の順で問題点と改善を押さえると理解しやすい。とくにテストでは手動DIの配線が増え、ボイラープレートが急増する。
-
MetroはKotlinコンパイラプラグイン実装のコンパイル時DI。Dagger/Anvil/Kotlin Injectの良さを統合し、Kotlinファーストで設計。
- 依存の「根」を持つDependency Graph(Dagger/Kotlin InjectのComponent相当)を定義し、コンパイラが実装を生成。
- 主要バインディング:
@Inject(クラス注入)、@Provides(提供関数)、@Binds(エイリアス)、Assisted Injection、Multi-binding(Set/Map)。Qualifier(@Namedなど)にも対応。 - Scoping:
@SingleIn(AppScope::class)を宣言すると、そのグラフのライフタイムで単一インスタンス(Daggerの@Singleton相当)。 - Provider/Lazyは注入側で要求するだけで、生成タイミングを遅延できる。
-
Metroではコンパイルは
compileKotlin一回で完了。生成はIR直書きでソースを出さないため、KAPT/KSPやJavaコンパイルの段増しを回避。大規模構成でのビルド時間を大きく削減。 -
Hiltでのコード生成がフェーズ増えまくって時間が増えてしまうという話。
https://youtu.be/jVfmtVKa604?t=1280 より
-
Anvilスタイルのアノテーション集約を内蔵。スコープキーに「貢献(contribution)」を束ね、グラフ側は「extends」するだけで取り込める。中間の「Merge系」定義は不要。
-
@ContributesBindingで“実装側から”上位型として公開できる。たとえばCacheImplをCacheとして束ねる用途が素直に書け、Multi-binding(@IntoSet/@IntoMap)にも対応。@ContributesBinding(AppScope::class, boundType = Cache::class) class CacheImpl @Inject constructor(/* ... */) : Cache -
プライベートなコンストラクタ/
@Providesも注入可能。プラグインがクラスを書き換えられるため、可視性を無理に広げる必要がない。 -
グラフ検証はTarjan(強連結成分検出)→トポロジカルソートで依存の有向非巡回性と初期化順を保証。循環や「似ているが違う」候補も含め、丁寧なエラーメッセージで原因と対処を提示。
-
Graph Extension(DaggerのSubcomponent相当)をサポート。親グラフのキーを再利用しつつ、子の依存とスコープを持てる。
-
IDE連携:K2のFIR/IRフェーズに深く統合し、IDEでの診断も可能。ただしIDE同梱Kotlinの版ズレで「ベストエフォート」。
-
既存プロジェクトへの導入:
- 逐次移行しやすいよう、Dagger/Anvil/Kotlin Injectのアノテーション互換(差し替え可能)。プロジェクト横断で双方向に依存(包含)させる戦略も取りやすい。
- Daggerの
Provider/Lazy/MembersInjectorともランタイムで相互運用。Anvilの@IgnoreQualifier等の拡張も理解可能。
-
パイプライン上のポイント:KSP/Javaのスタブ生成や複数回コンパイルを伴う従来構成(Dagger/Hilt+KAPT/KSPなど)に比べ、Metroはフロント(FIR)でヘッダ生成・集約、バック(IR)で実装生成に集約。
-
今後(発表時点の言及):Dynamic/Ad-hoc Graph、テスト用オーバーライド(0.7系の計画)、Kotlin 2.3でのマルチモジュール集約やインクリメンタルの最適化など。
Ask the speaker
(あんまり日本にいると会える方ではないのとGitHub上でめちゃくちゃ活動されていてすごいと思っていたので、色々つっこんで聞きに行きました)
-
FIR/IRの扱い:
- FIRはIDE都合のライフサイクルが独特(未完成コードを扱うため「なんでもnullable」的な設計)。IRはより低レベルで関数本体まで含むが直感的に追える。
- Kotlin Compiler Pluginの学習リソースとして、Kotlin公式のプラグイン例やカンファレンストーク(Brian Normanの“Writing your third compiler plugin”など)が有益。
-
KoinからMetroへの移行:
- 直接の移行ガイドは未整備だが、Dagger移行経験があれば大きな壁は少ない想定。難所はテストのStub/Override等「便利機能」の置き換え。
- 0.7で計画中のDynamic Graph/テストオーバーライドがあると移行は楽になる見込み(アノテーションプロセッサでは読めない式もコンパイラプラグインなら解析できるため、動的なモジュールリストからグラフ生成が可能)。
-
置換・除外:
- Anvil互換の「置換(replacements)」「除外(exclusions)」がMetroにもあり、テストで本物→偽物の差し替えや特定貢献の無効化が可能。
-
テスト観:
- モック多用を避け、実装/フェイクでテストする文化を推奨(SlackでもMockito脱却を進めた)。DIの目的はテスタビリティだが、依存の差し替え手段が整っていないと価値が半減する。
-
設計の考え方:
- 「まず理想のAPIを設計し、後から実装を追いつかせる」。利用者体験を最優先し、既存スタックとの明確なインタロップを用意する。
- 探究(rabbit hole)を恐れず、学びを積む姿勢が長期的な価値につながる。
既存アプリをアダプティブにするには:JetStreamの場合 / Extending application adaptability: A case study of JetStream
- 1つのバイナリで複数フォームファクター対応(アダプティブ):JetStreamはもともとTV向けアプリだったが、同じAPKでTV・モバイル(スマホ/タブレット/フォルダブル)・車載・XRまで動くように拡張。端末種別(isTablet等)で分岐せず、実行中のウィンドウサイズや利用可能な入力/表示領域に合わせてUIを更新する方針。
- tv-material → Compose Material 3へ置き換え:TV専用のUI部品(多様なカード、フォーカスの強調など)はモバイル向けComposeで作り直し。Material周りはライブラリごとに名前空間が異なりテーマ/カラースキームが共有されないため、混在させずに統一する。
-
IndicationNodeFactoryでフォーカス可視化:TVやキーボード操作では「今どこが操作対象か」が最重要。
Modifier.indicationを差し替える形で、フォーカス時の枠線表示・スケールアップ・背景色変更などのビジュアルエフェクトを描画(drawスコープで実装)。 - クリック入力の抽象化(タップ/Enter/D-Padが同一イベント):Composeではタッチ、外付けキーボードのEnter/Space、リモコン中央ボタンが同じ「click」として扱われる。
Modifier.clickable/onClickで一元ハンドリングでき、入力デバイスごとの特別対応が最小化。 - Media3への移行でステートレストレーションを簡素化:画面回転などの構成変更時もUI状態はViewModelで保持。動画再生だけはMedia3のプレーヤーをサービス化しつつExoPlayer互換APIで扱えるため、UI側の変更を最小にしつつ再生位置の保持やバックグラウンド継続が容易。
- Media3 UI(Compose版)でプレイヤー画面をフルCompose化:
Playerオブジェクトを渡すだけで再生/一時停止/シーク等の基本UIが用意され、従来のAndroid Viewラップが不要。 - レイアウト適応は「フィード」型に集約:カードのサイズ・段数をウィンドウ幅で調整。グローバルナビゲーションは入力手段で最適化(例:TV等のリモコン中心はTopBarに集約、タッチ中心はBottom Navigation/NavigationRail)。
- 段階的対応の前提:最初から最高到達点を狙わず、画面サイズ追従などの基礎から順に上げる(ガイドラインの「段階」を進めるイメージ)。
- 実装の規模感:少人数でも現実的に進められる。特にMedia3 UIの導入でビデオUIは短期間で構築可能。
そのAPI、誰のため? Androidライブラリ設計における利用者目線の実践テクニック
- 抜き出しライブラリーの落とし穴:内部前提(独自DI/多機能1関数/ドキュメント不足)が残ると“使いにくい”ライブラリになってしまう
- カプセル化と依存の制御:explicit API mode(可視性の明記を強制)、コンストラクタは利用者に必要な引数だけ公開、モジュール依存を外に漏らさない
- 前提の押し付け回避:特定DIやNavigation前提にせず、
@Composableひとつ置けば内部で遷移まで完結する入口を提供して前提を隠す - 高レベルAPI+低レベルAPIを併走して機能性と自由度を両立(普段は省コード、必要時だけ低レイヤで拡張)。最低限の運用はKDoc・軽いガイド・機能別サンプル+CI、そして作者自身が利用者として触って詰まりを潰す
まとめ
アプリのリードを引き継いで、今回は割と参加者として参加できて楽しめました。
自分の今後の開発を変えそうなものでいうと、Koog(AIエージェント作っているので)、AIのプロンプト関連とその評価、オンデバイスAIの戦略、自分の騙し方、AIのAPIのキャッシュ活用、Nav3、OpenTelemetry、Kotlin Compiler Pluginへの入り方、ライブラリのAPI設計方法など、さまざまな点で変化のきっかけを得られたなと思います。
いろんな形でDroidKaigiに関わっていただけた方々、ありがとうございました。










