
Android
イベント
マガジン
技術ブログ
Gemini At Workとは何か? Gemini At Workは、Google Cloudが主催する、企業での生成AI活用をテーマにしたイベントです。Geminiをはじめとする製品の情報に加え、企業の導入事例や、AIを業務に組み込む方法が紹介されます。去年2025年10月の開催では、企業向けAI基盤のGemini Enterpriseが発表されました。社内データとの接続、ノーコードでのAIエージェント構築、エージェントの一元管理を備え、組織全体の業務自動化を支える仕組みです。 さらに、AI学習プラットフォームのGoogle Skillsも発表されました。製品の進化と、それを使いこなす人材の育成という両面から、企業のAI活用を扱うイベントになっています。
「あ、こっちのリポジトリにスキル移植してなかったや。コピペしとこ」 Claude Code を使っていると、一度はやりますよね。 新規リポジトリに新しくハーネスを移植する際など、あまり良くないとわかりつつ、やりがちだと思います。 そこで今回は、株式会社エブリー Android チームでの、ハーネスの一元管理方法について紹介します。 3 つのリポジトリでコピペしていたハーネスを 1 か所にまとめ、合計 5,254 行の設定を消せました。 「本当は一元管理した方が良いのだろうな」と思っているあなたの一助になれば幸いです。 1. まとめ コピーした設定はズレていくため、一元管理した方が良い。 リポジトリごとの技術スタックの違いにより、単純な共通化は難しい リポジトリごとの大きな技術スタックの違いは、読み込む plugin を変えることで対応する リポジトリごとの小さな技術スタックの違いは、読み込むファイルを変えることで対応する 2. 背景 エブリーには、ネイティブで開発されている Android アプリが 4 つあります。 私がメインで触っているのは、そのうち 2 つと、Kotlin で書いた社内ツールの計 3 つのリポジトリです。 delishkitchen.tv www.docomo.ne.jp Issue を拾って Claude Code に PR まで作らせる、JVM で動く社内ツール 同じ Kotlin ですが、前提条件は違います。 デリッシュキッチン dバリューパス版 社内 JVM ツール 動く場所 Android Android JVM(Android SDK なし) UI XML View + Compose Android View なし DI Hilt + Koin 古い版の Koin のみ なし ビルド定義 Kotlin DSL + 共通ビルドロジック Groovy Kotlin DSL(単一モジュール) version catalog あり なし なし ktlint CI と hook で実行 なし CI で実行 この 3 つに、とりあえずハーネスをコピペしました。しばらく経ったころ、資産がいちばん多いデリッシュキッチンと社内ツールで、同じ名前のファイル 5 本を比べてみました。 同名のファイル デリッシュキッチン 社内ツール 差分(追加 + 削除) 実装手順のコマンド 591 行 530 行 977 行 セキュリティのレビュー用エージェント 151 行 72 行 202 行 Kotlin のレビュー用エージェント 113 行 77 行 174 行 コメントの点検スキル 81 行 75 行 141 行 レビューのコマンド 66 行 77 行 138 行 合計 1,632 行 行数が近いファイルでも差分は大きくて、両側でそれぞれ勝手に書き換わっていました。 ただ重複しているだけなら一方を消すだけで良いのですが、実際は リポジトリごとに中身が分岐 していました。 従って修正のたびに、どちらが正しいのか・どこまで反映すべきなのかを判断する必要が出てきました。 3. 原因 ではコピーした設定は、なぜ書き換わっていくのでしょう。 結論から述べると、 各々がよりハーネスを効かせようとした結果、コード分岐が進む ためです。 あるリポジトリに対して一番よくハーネスを効かせようとすると、具体的な記述が必要になります。 例) Manifest の exported を見る Compose の再コンポジションを見る 実際、Kotlin のレビュー用エージェントを比べると、デリッシュキッチン版には Compose の観点が、社内ツール版には「依存を増やさない」「JVM 17 で動く」など固有の観点が足されていました。 これらのファイルは当然、 技術スタックが違うので流用ができません 。 従って、各々がよりハーネスを効かせようとした結果コード分岐が進んでいくのです。 4. 解決策 DRY 原則に従い 1 つにまとめて配れば良い と、解決策として真っ先に思いつきます。これは正しいと思います。 ただ原因に記載した通り、技術スタックが違うので流用できないという問題を解決する必要があります。下記は私が考えた解決策です。 案 結果 共通部分のみ一元管理し、それ以外は各リポジトリ固有で持つ [不採用] 管理が簡単だが、共有できる箇所が少なく根本的な解決にならないため 技術の組み合わせごとに配布物を分ける [不採用] Compose 用のスキル、Hilt 用のスキル……と組み合わせの数だけ作成するため、配布物が膨大になる 共通の部分を層にして配り、技術ごとの知識はリポジトリの設定を見て読み分ける [採用] 具体的な記述は技術ごとに分けて持ち、どれを読むかをリポジトリごとに選ぶ 配布に関しては Claude Code の plugin を前提としました。 採用案は、大きな関心ごとは plugin を多層で分けます。その層の中で、詳細に関しては capability gate という仕組み (後述) を用いて選択させます。 切り替え 何で決めるか 例 層 どのリポジトリが使うか Kotlin 共通 / Android 共通 / ドメイン固有 capability gate そのリポジトリがどの技術を使うか Compose / Android View / ktlint の有無 4.1 層 どのリポジトリが使うかで、層を分けます。今回の場合、層は Kotlin 共通・Android 共通・ドメイン固有の 3 つで分けました。依存の向きは「ドメイン → Android 共通 → Kotlin 共通」です。 層の境界は、次の基準だけで決めました。 そのハーネスは、 その層を使うリポジトリのうち、使っている技術がいちばん少ないところ でも成り立つか。 層 前提にしてよい 前提にしてはいけない Kotlin 共通 Kotlin、Gradle、git / GitHub など Android SDK、宣言的 UI、DI コンテナ、ビルド定義の書き方、特定の lint ツール Android 共通 Android、Activity / Fragment / ViewModel など 宣言的 UI、特定の DI、ビルド定義の書き方、version catalog ドメイン固有 自社ドメインの業務知識 あらゆる技術スタック これで Android SDK の採用など、大きな技術スタックの違いによる分岐に対応できます。 例えば Android SDK 非採用の JVM ツールでは、Kotlin 共通の plugin を入れることで、Android 関連のコンテキストを無視できます。 それだけでなく、JVM ツール開発時に行ったハーネス修正の知見が、より深い層の plugin を使用するリポジトリにも適用されるのです。 しかしながら、これだけではまだ分岐に対応しきれていません。 例えば Android の UI に関してのレビュースキルを考えます。リポジトリ A では Compose を、リポジトリ B では XML View を使用している場合、それぞれに同じスキルを使用すると、お互いにノイズとなるコンテキストが含まれてしまいます。 ここで 4.1 と同じやり方で Compose 層、XML View 層と分けてしまうと、層の数が膨大になり、依存関係が複雑になることで、管理が難しくなります。 この層の中の違いを吸収するのが、次の capability gate です。 4.2 Capability gate capability gate は、層の中の違いを吸収し、分岐に対応する仕組みです。 使用ライブラリなどの細かい技術スタック (capabilities) を、リポジトリ内に設定ファイルとして持ちます。 実行時にこの設定ファイルを読み、AI コンテキストを切り替えます。 ディレクトリ構成は、下記を想定しています。 技術ごとの観点を SKILL.md 本文から丸ごと抜き出し、 reference/ 配下へ保存します。 SKILL.md の description に分岐条件を記載することで、コンテキストの分岐に対応します。 ui-layer-review/ ├── SKILL.md # 手順と、技術に依らない観点だけ └── reference/ ├── compose.md # Compose の観点 └── xml-view.md # XML View の観点 設定は独自の json ファイルとして保持しています。 { " capabilities ": { " platform ": " android ", " ui ": [ " xml " ] , " di ": [ " koin " ] , " versionCatalog ": false , " lint ": [] } } capabilities.ui の値と、読む reference の対応は次のとおりです。 capabilities.ui 読む reference 振る舞い "ui": ["compose"] compose.md Compose の観点でレビューする "ui": ["xml"] xml-view.md XML View の観点でレビューする "ui": ["compose", "xml"] 両方 差分のファイル種別で当てる観点を決める "ui": [] なし UI レイヤーを持たないリポジトリとして指摘せずに終わる 未設定 なし 未適用としてレビューする 5. 結果 リポジトリ + - デリッシュキッチン 183 2,129 dバリューパス版 446 2,032 社内 JVM ツール 255 1,093 合計 884 5,254 足した行に関しては、主に下記です。 CLAUDE.md の修正 技術スタックの設定ファイル追加 リポジトリ固有ハーネスの書き直し そのリポジトリ固有の情報を持つファイル以外は、一元管理できるようになりました。 これで Claude Code のハーネスを別リポジトリからコピペすることは、ほぼ起きなくなったと思います。 6. おわりに エブリーの Android チームでは、今後 Loop Engineering を導入し、真剣にプロダクト開発速度 10 倍を目指しています。 このハーネスの一元管理化も、その一環として行いました。ハーネス自身の開発速度も向上したため、一歩前進です。 道のりはまだまだ、大変厳しいと思いますが、高い目標に向けて駆け抜けたいと思います。 この記事は、株式会社エブリーで Android アプリ開発を担当している岡田が書きました。 エブリーでは、ともに働く仲間を募集しています。 corp.every.tv
こんにちは。LIFULL HOME'S アプリケーションの開発を担当している七尾です。 私たちのチームでは、LIFULL HOME'S アプリケーションのビジネスロジックを Kotlin Multiplatform (KMP) で共通化する取り組みを進めています。 これまで Android と iOS はそれぞれ別々にビジネスロジックを実装していました。そのため両 OS の間で仕様や機能に差分が生まれ、ユーザー体験にも差が出ていました。同じ機能を修正・追加するたびに、Android と iOS の両方で作業をする必要もありました。 この課題を KMP によるロジック統一で解決してきました。本記事では、導入に至った背景と、統一によって得られた効果、そして現在の状況を紹介します。 KMP 導入の経緯や技術選定の理由については、以前に別の記事で詳しく紹介しています。合わせて読んでいただけるとうれしいです。 LIFULL における Kotlin Multiplatform(KMP) 前回の記事の時点では、KMP は導入を始めたばかりで「これから移行を進めていく」という段階でした。あれから時間が経ち、ビジネスロジックの共通化はチームに定着してきました。本記事では、その後さらに取り組みが進んだ現在の状況をお伝えします。 同じようにマルチプラットフォームでのアプリケーション開発に課題を感じている方の参考になれば幸いです。 KMP導入前に抱えていた課題 両OSで別々に実装されていたビジネスロジック 修正・機能追加のたびに2倍の作業が発生する KMPによるロジック統一で得られた効果 作業量の軽減 OSの越境が可能になった 現在の取り組みと今後の展望 アプリケーション全体でのKMP導入を目指して UI の共通化にも着手 まとめ KMP導入前に抱えていた課題 両OSで別々に実装されていたビジネスロジック これまで、LIFULL HOME'S アプリケーションのビジネスロジックは Android と iOS でそれぞれ実装されていました。 同じ機能を実現するためのロジックが 2 つ存在するため、次のような問題が起きていました。 両 OS の間で仕様に差分が生まれる 機能そのものに差分が生まれる 結果として、Android と iOS でユーザー体験に差が出てしまう 同じアプリケーションでありながら、使う OS によって体験が変わってしまう状態でした。 修正・機能追加のたびに2倍の作業が発生する ビジネスロジックが 2 つ存在していることは、開発の効率にも影響していました。 同じ機能を修正したり機能追加したりする場合、Android と iOS のそれぞれで作業をする必要があります。同じ内容の実装を 2 度行うことになり、作業量が増えていました。 さらに、片方の OS だけ修正が漏れると、前述の仕様差分・機能差分がさらに広がってしまうリスクもありました。 KMPによるロジック統一で得られた効果 こうした課題を解決するために、ビジネスロジックを Kotlin Multiplatform (KMP) で共通化する取り組みを始めました。 KMP を使うと、Kotlin で書いたロジックを Android と iOS の両方で共通のコードとして利用できます。ビジネスロジックを 1 つに統一することで、次の効果が得られました。 作業量の軽減 ロジックが 1 つになったことで、修正や機能追加を 1 度実装すれば両 OS へ反映できるようになりました。同じ実装を 2 度行う必要がなくなり、作業量を減らせました。 仕様差分・機能差分が生まれにくくなり、両 OS で同じ体験を提供できるようになった点も大きな効果です。 たとえば、ホーム画面や物件一覧画面では、検索条件・お気に入り・閲覧履歴といったビジネスロジックを KMP で共通化しています。以前はこうしたロジックを Android と iOS でそれぞれ実装していたため、片方で仕様を変えるともう一方にも同じ修正を入れる必要がありました。共通化した今は、ロジックの修正が 1 回で両 OS に反映され、仕様がずれる心配もなくなっています。 テストの面でも効率化が進みました。共通ロジックに対してユニットテストを書いておけば、そのテストが両 OS のロジックの品質を担保してくれます。これまで Android と iOS でそれぞれ書いていたビジネスロジックのテストを、一度書けば済むようになりました。 さらに、施策を展開するスピードも上がりました。たとえば片方の OS で先に A/B テストを行い、効果が良かった施策をもう一方の OS にも展開する、という進め方ができるようになりました。ロジックが共通化されているため、良かった施策を両 OS へ広げる際の実装の手戻りも抑えられます。 こうした共通化を積み重ねる中で、「同じロジックは 1 つにまとめる」という意識がチーム全体に定着してきました。新しい機能を作るときも、まず共通化できる部分がないかを考えるのが当たり前になっています。 OSの越境が可能になった ロジック統一によって得られた効果は、作業量の軽減だけではありませんでした。 これまで Android のロジックは Android エンジニアが、iOS のロジックは iOS エンジニアが担当していました。しかし共通ロジックが Kotlin で書かれたことで、両 OS のエンジニアのどちらでもビジネスロジックの作業ができるようになりました。 つまり、担当 OS の垣根を越えた「OS の越境」です。前回の記事では、これは「これから実現していきたいこと」として書いていました。あれから時間が経ち、今では実際のチームの働き方として根付いています。 具体的には、次のような変化がありました。 iOS エンジニアも Kotlin で共通ロジックを実装するようになった Android エンジニアも、共通化されたロジックを通じて iOS 向けの機能開発を担うようになった ビジネスロジックのレビューを、両 OS のエンジニアで回せるようになった KMP 導入前は、iOS エンジニアが Swift、Android エンジニアが Kotlin という形で、担当する言語や OS が切り分けられていました。共通ロジックが Kotlin に統一されたことで、iOS エンジニアが Kotlin を書くことも、Android エンジニアが iOS の機能に関わることも、特別なことではなくなっています。レビューも両 OS のエンジニアで参加できるため、特定の担当者に負荷が偏りにくくなり、知見も共有しやすくなりました。 実際、共通ロジックのリポジトリの開発履歴を振り返ってみると、iOS 固有のコードに手を入れたメンバーは、ほぼ全員が Kotlin の共通ロジックにもコミットしていました。共通ロジックに関わるメンバーの数も、導入初期から年々増え続けています。越境が一部の人の試みから、チーム全体の当たり前へと広がってきたことが、履歴からも読み取れます。 この 2 年半で一番変わったと感じるのは、Android と iOS を「完全に別のもの」としてとらえる意識が薄れてきたことです。新しい機能を考えるときは KMP での共通化を前提とし、両 OS へ入れ込める形で開発を進める。それが今では当たり前の進め方になっています。 現在の取り組みと今後の展望 アプリケーション全体でのKMP導入を目指して ビジネスロジックの共通化は主要な画面に広がり、チームに定着してきました。現在は、一部のロジックにとどまらず、アプリケーション全体での KMP 導入を目指して取り組みを進めています。 共通ロジックは専用のリポジトリにまとめています。ここでは Clean Architecture に基づき、責務ごとにモジュールを分割しています。 data : データ取得の実装やネットワーク通信、ローカルストレージ domain : ユースケースなどのビジネスロジック common : 日時処理やユーティリティなどの共通処理 ネットワーク通信には Ktor、ローカル保存には Multiplatform Settings や SQLDelight といった KMP 対応のライブラリを採用しています。 各 OS への提供方法は少し異なります。iOS へは共通コードを XCFramework という形式にまとめて提供し、Android へは各モジュールを AAR という形式で個別に取り込む形で連携しています。どちらも、同じ Kotlin のロジックをそれぞれの OS のビルドに取り込むためのしくみです。 移行を進める中では、共通化する範囲の見極めや、リポジトリをまたいだ開発体験の改善など、向き合うべき課題も見えてきています。こうした点を一つずつ整理しながら、アプリケーション全体への展開を進めています。 UI の共通化にも着手 ビジネスロジックの共通化が定着してきたことを受けて、最近は UI 層についても Compose Multiplatform (CMP) を使った共通化に着手しています。ロジックだけでなく UI まで共通化できれば、両 OS での体験の差をさらに小さくできます。 こちらはまだ始まったばかりの取り組みですので、詳しくはまた別の機会に紹介できればと思います。 まとめ Android と iOS で別々に実装していたビジネスロジックを KMP で統一することで、作業量の軽減と OS の越境という効果を得られました。前回の記事の時点では「これから移行を進めていく」段階でしたが、今ではビジネスロジックの共通化がチームに定着し、主要な画面に広がっています。 現在はアプリケーション全体での KMP 導入を目指しつつ、CMP による UI の共通化にも着手し始めています。マルチプラットフォームでのアプリケーション開発を、より効率的で、ユーザー体験の差が出ないものにしていきたいと考えています。 最後に、LIFULL ではともに挑戦していける仲間を募集しています。ご興味をお持ちいただけましたら、ぜひ以下のページもご覧ください。 hrmos.co hrmos.co ※ 今回紹介した LIFULL HOME'S アプリケーションはこちらです。 LIFULL HOME'S アプリケーションの紹介ページ


















