ソフトウェア エンジニア
今年、Android はすべての UI の構築において Compose ファーストに正式に移行し、従来のビューはメンテナンス モードに移行しました。Google Play では、この取り組みを 2020 年に開始しました。20 年近くにわたるエンジニアリングの歴史と数十億人のユーザーを抱える中で、Jetpack Compose への移行は、実証済みのベスト プラクティス、コア Compose チームとの緊密な連携、継続的なツールの進化によって実現されました。
Compose を使用して Google Play をモダナイズすることで、速度と長期的な安定性がどのように向上したかをご覧ください。このセッションでは、エージェント型の Figma デザイン ワークフローを Compose の実装に接続し、品質を自動化して不要な再コンポーズを防ぎ、ダイナミック カラー、豊富なタイポグラフィ、レイヤード シェイプを使用して表現力豊かなインターフェースを作成する方法について説明します。このセッションでは、バグのトリアージを迅速に行い、Compose への移行を自信を持ってスケールするための、検証済みの戦略をご紹介します。
ComposeView を直接ホストする方向に進むことができます。MaterialTheme オーバーレイと Material Color Utilities ライブラリを使用して、完全にコード内で動作します。これにより、Activity の破棄を回避できます。ただし、ビルド時にカラーパターンを生成すると、ランタイム レイテンシを制限できます。BoxWithConstraints などのパフォーマンス上の潜在的な問題を報告します。文字起こし
皆様、ようこそ。
Jon と申します。
Google Play ストア チームから参加しています。
こちらは同僚のキリルです。
本日は、View と Fragment のシステムから Compose に多くのコードを移行した方法についてお話しします。
さっそく始めましょう。
その導入はどのように進み、どのような規模だったのでしょうか。
私たちのアプリのコードベースは、2007 年頃から有機的に成長してきました。
本番環境のコードは数百万行を超え、200 の画面と 500 の独自の UI コンポーネントがあり、それらがさまざまな画面で使用されています。
2020 年に、デベロッパーのベロシティ、信頼性、パフォーマンスが、私たちが望む目標を達成できなくなっていることに気づきました。
そこで、最新の UI 技術スタックを採用することにしました。
さまざまなライブラリを評価しました。
当時、AndroidX の仲間から Compose について話を聞き、Compose を採用して Compose の最初の主要パートナーになることを決めました。
いかがでしたか
6 年が経過した現在、Google Play ストア アプリ内のユーザー インプレッションの 97% が Compose のみの画面で発生しています。
2020 年以降、スマートフォンと大画面の開発はすべて Compose のみに移行しています。
さらに、XR、TV、Auto、Wear などの拡張フォーム ファクタも、2025 年以降は Compose に移行しています。
Compose はその成功を強力に後押しするものであり、私たちが Compose の採用を拡大し続けている理由でもあります。
非常に強力で包括的なテストスイートが用意されているため、週に 2 回、Compose の新しいバージョンを導入することができました。
アルファ版ビルドと新しい API を取り込むことで、新しいバグ修正やパフォーマンス改善を活用し、新しい API を探索して新しいユーザー エクスペリエンスを提供できます。
特にパフォーマンスに関して言えば、Compose は、すべての主要な画面でアプリのジャンク パフォーマンスを継続的に提供しています。
検索結果ページ、ホームページ、詳細ページなどです。
パフォーマンス、ジャンク、読み込みレイテンシは、ビジネスにとってもユーザー エクスペリエンスにとっても非常に重要な指標です。
過去 14 か月間で 30% の改善が見られました。
では、どのようにしてポケモンは進化してきたのでしょうか。
本日は、この点についてお話しします。
では、移行計画について説明します。
そこで、Google Play は複数のステップからなるアプローチを採用しました。
私たちは、いわば、すべての卵を 1 つのバスケットに入れるようなことはしたくありませんでした。
私たちは、リスクを理解し、それがビジネスにどのように影響するかを把握し、本格的に導入する前に自信を深めるために、いくつかのステップを踏むことにしました。
そこで、crawling、walking、jogging、running という名前を付けます。
そのため、ハイハイの段階では、Compose に慣れることが重要でした。
まず、トラフィックの少ないページから始めました。
たとえば、設定ページやサードパーティ ライセンスなどです。
主な目標は、Compose ライブラリに慣れることです。
行、列、テキスト、画像など、さまざまな要素に慣れることができます。
そして、アプリのインフラストラクチャをすべて取り込み、Compose の方法でどのように機能するかを理解する必要があります。
インプレッションのロギング、クリックのロギング、テーマ設定など、さまざまな作業があり、同時にデータ読み込みの刷新も行っていました。
これらすべてが該当します。
特に役立ったのは、すべての画面の一貫性を確保することでした。Compose のテーマ設定オブジェクトが XML のスタイル、タイポグラフィ、色などに直接マッピングされることを確認するために、単体テストと機能テストをいくつか作成しました。
そこで、ある程度慣れてきて、快適に感じられるようになったら、ウォーキングに移行することにしました。
この場合、トラフィックが非常に多い画面の小さなコンポーネントを対象に、移行がどのようなものかを評価しました。
RecyclerView 内のアイテムを使用することをおすすめします。そうすることで、View システムと Compose システムの互換性に関する懸念事項を把握し、パフォーマンスとジャンクを測定できます。
コンポーネント自体はそれほど複雑である必要はありませんが、レイアウト、テキスト、画像など、通常目にするいくつかの異なるインスタンスを試すことができるものが必要です。
この領域で注意すべき点、そしておそらく予想されるのは、ジャンクとレイテンシの増加です。これは、2 つのスタックを実行しているためです。
Compose のレンダリング スタックと、View のレンダリング スタックがあります。
そのため、メモリのスラッシングや、余分なクラスの読み込みなどが発生します。
しかし、Compose の導入が進むにつれて、この問題は解消されます。
それまでの間にどうしても必要な場合は、その画面を少しプリウォーミングすることで、短期的にはコストを抑えることができます。
ウォーキングの後はジョギングに移ります。
これは、私たちが非常に快適になった後のことです。
トラフィックの多いページで 1 つのコンポーネントをテストするのではなく、トラフィックの多いページ全体をテストします。
そこで、ナビゲーション ライブラリとフラグメントはそのまま使用し、個々の画面は Compose で完全に実装しました。
Fragment が ComposeView 自体を返すようにしました。
これにより、レイテンシとジャンクが改善されます。
前述したようなメモリのスラッシングやクラスの読み込みはそれほど発生しません。
また、Compose 自体も ComposeView レベルで、ある程度のキャッシュを行います。
そのため、一貫性のある外観で同じアイコンを持つ可能性のあるカードが多数ある RecyclerView がある場合、アイコンの解析とリソースの使用はすべて ComposeView 内のすべての要素で共有されます。
その後、ランニングに移行しました。
この段階で、本格的に取り組みます。
アプリのトラフィックの大部分が Compose からのものになったら、次のレベルに進みます。
画面の上にはアクティビティがあります。
そのため、ComposeView をアクティビティのルートに移動します。
コードの多くは比較的似たものになります。
ナビゲーション指示を実行しようとしています。つまり、ある画面から別の画面に移動しようとしています。
ただし、Fragment トランザクションを行うのではなく、コンポーズ可能な関数を呼び出します。
これは、Nav2 ライブラリまたは Nav3 ライブラリのどちらでも行うことができます。
私たちの場合は、すべての画面を移行したわけではありません。
先ほど、たくさんの種類があると申し上げました。
そこで、Fragment ベースの画面を構築し、Compose の AndroidFragment ラッパーを活用してこれを実現できる Fragment 互換性を構築しました。
繰り返しになりますが、ジャンクとレイテンシはビジネスにとって重要であるため、Google はその限界を押し広げ続けています。画面をまたいだキャッシュ保存が可能になり、メモリのスラッシング問題が大幅に軽減されたため、今後も改善が期待できます。
しかし、新しい機能も利用できます。
右側の GIF に例を示していますが、画面間で共有要素遷移を行うことができます。
そこで、画面を移動する際に画像を拡大して、連続性を表現しています。
また、移行全体で Compose のアニメーションの柔軟性をすべて活用することもできます。
それでは、Kirill にバトンタッチして、その他の機能について説明してもらいます。
Jon さん、ありがとうございます。
私がしばらく取り組んできた、Android アプリのテーマ設定とスタイル設定という、私にとって非常に身近なトピックについて少しお話ししたいと思います。
ビューの世界には、Java コードと、少し後に Kotlin コードがあり、さらに X-- XML リソース(色、文字列、ディメンション、レイアウト、テーマ、スタイルなどのリソース)がありました。
その仕組みは、これらの XML リソースがすべてコンパイルされて、APK に含まれるリソース テーブルになるというもので、ほぼ静的なものでした。
API Resources.Theme.applyStyle を使用すると、実行時に動的に UI 階層にカスタム スタイルを適用できます。
主な制限は、コンテンツをインフレートする前にこれを行う必要があることでした。つまり、既存の UI にそのスタイルを動的に適用する場合は、Activity 全体を破棄して再作成する必要があり、ユーザー エクスペリエンスの観点から好ましくありませんでした。
また、このプリコンパイルされた XML ブロックを適用していたため、テストフラグやその他のビジネスニーズに基づいて、複数の条件付き動的オーバーレイを組み合わせることが難しくなっていました。
XML の世界では、スタイル階層の適切なメンタルモデルを構築するのが困難でした。
最後に、Android Studio やその他のデベロッパー ツールには、Java の世界、コードの世界、XML の世界の間にあるギャップを埋めるような、単一の統合ビューがありませんでした。
適切なメンタルモデルを構築して、スタイルが適用されない理由や、間違った色が表示される理由などをデバッグするのは、少し大変でした。
新しい世界、つまり Compose の世界では、特にマテリアル デザイン システムと Compose Material ライブラリ、そして他のデザイン システムでも、マテリアル デザイン システム向けに、画面の UI 階層の最上位に適用する MaterialTheme コンポーザブルが用意されています。この MaterialTheme は、UI のより小さな部分にオーバーレイとして適用することもできます。
右側の画面を見ると、上部に Play Points ゴールドが表示されています。これは、進行状況インジケーターといくつかのテキストが表示された小さなカードです。
このマテリアル テーマ オーバーレイは、GitHub で入手可能な Material Color Utilities ライブラリによって生成されたカスタム カラーパターンを使用して適用します。
この茶色がかった黄色、ゴールドの色をシードとして渡すと、マテリアル カラー トークンの全スペクトルが生成されます。プライマリはプライマリ、セカンダリはセカンダリで、このカードの実装内で使用されます。
これにより、Android デベロッパーは完全なダイナミック テーマ設定機能を利用できるようになります。
すべてがコードで行われ、すべてが Kotlin で行われます。つまり、すべてがどのように動作するかについて適切なメンタルモデルを構築するのがはるかに簡単になり、デバッグもはるかに簡単になります。
内部的には、これらのオーバーレイは、適用先として選択した階層内の特定の UI ノードにコンポジション ローカルとしてアタッチされます。
パフォーマンスに関する考慮事項: Material Color Utilities ライブラリを使用して完全なカラーパターンを生成するには、費用がかかります。
ただし、一定のオーバーヘッドが伴います。
そのため、複雑な処理が必要で、複数のカラーパターンが必要な場合、たとえば弁当箱のようなものや、プロダクトのニーズに応じて、実行時に複数のカラーパターンを生成する場合、それが積み重なり、レイテンシやジャンクが増加します。
そのため、a)パフォーマンスへの影響を測定し、b)このオーバーヘッドの一部をランタイムからビルドタイムにオフロードする必要があります。
ビルド時に同じ Material Color Utilities ライブラリを使用して、ブランディングのニーズやプロダクトのニーズに基づいてこれらのカラーパターンを事前に生成し、このオーバーヘッドをビルド時にオフロードできます。
UI 階層ツリー内のオーバーレイの正確な位置を追跡する CompositionLocal は、はるかに低コストですが、それでも無料ではありません。
そのため、使用する数(数十、数百)によっては、費用がかさむことになります。
そして、ある時点で、これらすべての CompositionLocal を組み合わせたコストを検討する必要があります。
これは、Compose の真の動的なランタイム テーマ設定機能の例です。
4 つの象限があり、同じビジュアル、同じイベントカードがあります。
カード全体に広がるヒーロー画像があり、左上にラベル、右下にボタン、テキストがいくつかあります。
各象限の右側には、各要素のデザイン仕様として使用されるカラートークンのコールアウトが表示されます。
カードの実装は、これら 4 つすべてでまったく同じです。
唯一の違いは、カードをラップするマテリアル テーマ設定 オーバーレイが、画像の主要な色(青、紫、黄、赤)に基づいて動的に作成されたカラーパターンで生成または構成されていることです。
それ以外はすべて同じです。
アクティビティを破棄して再作成する必要はありません。
テストフラグやその他のビジネスニーズに基づいて、条件付きで適用できます。
また、先ほども述べたように、アプリに色のカスタマイズを適用する、よりダイナミックで真にダイナミックな世界です。
それでは、次のセクションについて Jon から説明します。
うん
これは、Kirill が話していた柔軟性の一部です。
信頼性と速度について説明します。
そのため、高品質なコードを迅速に構築することは、すべてのデベロッパーと企業が求めていることです。
では、Compose がどのようにして Google Play でそれを実現したのかを見ていきましょう。
まず、この大きな図で示されているように、アーキテクチャのシンプルさは、人間であれエージェントであれ、すべてのコード作成者とメンテナーにとって大きなメリットとなります。
これについてはあまり深く掘り下げませんが、Compose が提示しようとしているメンタルモデルの一つは単方向データフローに関するもので、これは私たちのチームに大きな影響を与えました。
仕組みに詳しくない方のために説明すると、基本的にはデータソースがあります。
データソースはデータを提供します。
そのデータは特定の UI に変換され、UI モデルとして出力されます。
Compose は、その状態の value プロパティを呼び出すだけで、簡単に状態を監視できます。
これは、この図の上部にある矢印のようなものです。
イベントは逆方向に流れます。
ユーザーがボタンをクリックすると、UI 変換ツールに戻り、データソースが更新されて新しい値が出力され、変換が実行されるというループが発生します。
つまり、すべてがこの比較的シンプルなループを通過します。
この取り組みから得られたメリットは、立ち上げ期間の短縮に役立つことです。
メインスレッドや他のスレッドで発生している呼び出しをすべて把握する必要はありません。
1 つの一般的なループに従います。
Compose を使用すると、データのモニタリングが非常に簡単になり、アーキテクチャ レイヤ全体でデータの強力な整合性が保証されます。
このメンタルモデルにより、チーム内での信頼できる情報源に関する会話が減り、いくつかの種類の競合状態が完全に解消されました。
もう 1 つの大きなメリットは、以前のモデルビュー コントローラ アーキテクチャと比較して、データソースと UI 変換の並列実行が非常に簡単になったことです。
これにより、ストア内の読み込みレイテンシが 50 ミリ秒ほど改善されました。
これは非常に印象的でした。
単方向データフローと並ぶ Compose のもう一つの大きな特徴は、宣言型 UI です。
キリルが少し触れていましたが
Google Play デベロッパーにも好評です。
UI コントロール フローが 1 つのファイルにまとめられました。
右側に、かなりわかりやすい例があります。
@Composable
fun PurchaseRow(onClick: () -> Unit, modifier: Modifier) {
Row(modifier) {
Text("20% off sale")
if (LocalExpts.current.useButtonV2) {
ButtonV2("Buy", onClick = onClick)
} else {
ButtonV1("Buy", onClick = onClick)
}
}
}テストフラグに基づいて、2 つのボタン実装を切り替えています。
複数のビューファイルや、フラグメント ファイル、XML ファイルを読み込む必要がなく、上から下まで読み込むだけで済むため、はるかにシンプルになりました。
コンポーザブルをネストするコストも、ネストされたビュー階層よりもはるかに低く、パフォーマンスを少しでも向上させるために苦労する必要がなく、コードを自然に配置して読みやすくすることができました。
他にも、ライフサイクル コールバックが大幅に簡素化され、考慮すべきコールバックの数が減ったというメリットもあります。
ビューのライフサイクル、フラグメントのライフサイクル、アクティビティのライフサイクルを意識する代わりに、DisposableEffect や副作用を使用します。これらは、配置場所に応じて非常に一貫した方法で動作します。
これは、私たちにとって大きなメリットとなっています。
この 2 つのアーキテクチャの変更が、LLM の基盤を築き、非常に高品質なコードの生成を可能にしたと考えています。
皆さんの多くは、お気に入りの LLM でこれを試したことがあるのではないでしょうか。
色やタイポグラフィで注釈を付けたスクリーンショットをフィードすると、行、列、テキスト、ボタンを生成できます。
ただし、制限事項があります。
Play が実際に確認したのは、コードベースを理解していないということですよね?
再利用すべき既存のコンポーネントや、デザイン トークンの名前は把握していません。
ハードコードされた色と文字列が提供されるだけです。
デバイスから取得するコンテンツとサーバーから取得するコンテンツをどのように分けるか、LazyList を使用してパフォーマンスを確保し、巨大な列を作成しないようにするにはどうすればよいか、などです。
そこで、これらの問題に対処するために、スクリーンショットにメタデータを追加しました。
UI 名、コンポーネントの開始位置と終了位置を把握するための境界ボックス、色とタイポグラフィのセマンティック名、スキルのデザイン トークン、そしてパフォーマンスを考慮できるように、どこに配置されるかを示すプロンプトも含まれています。
また、非常に大まかな関数シグネチャも指定して、どのコンテンツが動的で、どのコンテンツが静的かを伝えることができます。
その結果、以前のような状態になり、その後へと移行します。
図が煩雑になるため、ここでは境界ボックスは示していません。
しかし、ハードコードされた色とタイポグラフィの名前が、title large と on surface に置き換えられていることがわかります。
カードヘッダーやアイコンバッジなどのコンポーネント名が明確に示されています。
そうすれば、コードベースでそれらを見つけることができます。
また、これらの多くは共通のデザイン ライブラリから取得されています。これについては、キリルが後ほど説明します。
そのため、これらすべてに常にカスタムラベルを付けるわけではありません。
これを、下部にある OfferCard のようなメソッド シグネチャと組み合わせると、タイトル、endDate、画像がすべて動的であることが認識されます。
これらをハードコードすべきではありません。
これらのセマンティック名とともに、Kirill が説明したさまざまな色のアプリケーションに切り替えることができます。
そして、より多くのコードを生成しています。
ここからは Kirill にバトンタッチして、品質を維持するための取り組みについて説明してもらいます。
Jon さん、ありがとうございます。
LLM とエージェントによる開発支援というトピックの続きとして、まずは UX 設計側から始めます。
Figma など、どのデザインツールを使用する場合でも、すべてのデザイナーが使用する共有済みテンプレートを用意し、選択したデザイン システムのカラー トークン、タイポグラフィ トークン、シェイプ トークンを含める必要があります。
マテリアルかもしれません。
カスタム設計システムかもしれません。
特にカラートークンについては、ライトモードとダークモードの切り替え、および数年前に Android レベルで導入された標準、中、高コントラスト モードの切り替えを、テンプレートに組み込みでサポートしたいと考えています。
デザイナーは、これらの異なる組み合わせをすばやく切り替えて、デザイン仕様がどのように反応するかを確認できます。
そして、最終的に実装側に引き渡される際、そのエクスポート パイプラインで、すべての色、タイポグラフィ要素、形状が、ハードコードされた RGB 値、フォント ファミリー、フォントサイズではなく、一致するトークンを使用していることを追跡できる機能が必要です。
最後に、Google Play の規模で非常に役立ったのは、数十どころか数百ものコンポーネント、クラスタ、カード、コンテナを、さまざまな画面で使用できることです。
これらすべてを 1 か所で追跡すると便利であることがわかりました。これをコンポーネント カタログと呼び、そのコンポーネント カタログでトレーニングされたカスタム エージェントを用意します。
このエージェントが役立つのは、新しい画面、新しいユーザー ジャーニー、新しいプロダクト分野を開始するときです。最初のデザイン探索(スライドや Figma の仕様など)から、そのエージェントにフィードすると、エージェントはすでに存在するコンポーネントをすばやく特定できるため、再設計や再仕様化の必要がなく、ゼロから再実装する必要もありません。
これにより、設計コストと実装コストを大幅に削減できます。
実装面では、コア Lint ルールとカスタム Lint ルールを多用しています。
特に注目していただきたいコア Lint ルールが 2 つあります。1 つは、不要な再コンポジションの原因を特定するのに役立つ FrequentlyChangingValueDetector です。不要な再コンポジションは、レイテンシやジャンクにつながる可能性があります。もう 1 つは、Material Compose Scaffold コンポーザブルを使用しているものの、受信パディングを無視しているケースにフラグを立てる ScaffoldPaddingDetector です。
これはレイテンシやジャンクの原因にはなりませんが、場合によっては、デバッグが非常に困難なレイアウトの不整合につながる可能性があります。たとえば、エッジツーエッジや、折りたたみ、展開などの場合です。
最後に、BoxWithConstraints には、パフォーマンスに関する考慮事項がいくつかあります。
すべてを適切にレイアウトするには、2 つのフレームが必要になる場合があります。
完全にブロックするわけではありませんが、Lint 警告としてフラグを立てることで、その画面を開発しているデベロッパーが、BoxWithConstraints を使用するという特定の選択をしていることを認識できるようにしています。また、その特定のレイアウトに別のものを使用することもできます。
テーマ設定の自動化には、色トークン、タイポグラフィ トークン、シェイプ トークンを使用します。
ハードコードされた色とハードコードされたタイポグラフィの使用をブロックする Lint ルールがあります。
マテリアル 2 コンポーネントの使用にフラグを付ける Lint ルールがあります。
最新かつ最高の Material 3 のみを使用します。これには、昨年と今年に導入された新しい表現力豊かなコンポーネントが含まれます。
たとえば、以前の XML ファイルから色を読み取れないようにするカスタム Lint ルールがあります。
新しい画面では Compose ファースト、Compose のみとしたい。
また、Text コンポーザブル(特にマテリアル デザイン システム)には、スタイルと色のフォールバックがあります。
これらはローカル コンテンツの色とローカル タイポグラフィから読み取られますが、それに依存したくはありません。
すべてのテキストで、デザイン仕様の特定のスタイル、タイポグラフィ スタイル、特定の色を意図的に使用する必要があります。
これは、Google が用意している Lint ルールです。
最後の 1 つは、ScaffoldPaddingDetector と同様に、すべてのコンポーズ可能な関数が入力修飾子を使用することを強制するカスタム Lint ルールです。
繰り返しになりますが、新しい使用方法が誤って配置されたり、入力された背景が尊重されなかったりするのを防ぐためです。
このプレゼンテーションで皆様に覚えていただきたい主なポイントは、この 3 つです。
1 つ目は、2026 年末の時点で、Compose は大規模な実績を持つ成熟した UI ツールキットであるということです。
Android 自体が Compose ファーストです。
今年初めの Google I/O で、すべての UI ライブラリとすべてのエージェント フローが Compose ファーストになることを発表しました。
また、Jon がプレゼンテーションの前半で述べたように、段階的に移行することをおすすめします。特に大規模なコードベースでは、開発者フローとエージェント フローの速度を向上させるために、段階的に移行することをおすすめします。
本日はご参加いただきありがとうございました。この後、私とジョンが外で皆様からのご質問にお答えします。
お時間をいただき、ありがとうございました。