SwiftUIアプリにUaaLを組み込む ── Viewのライフサイクル管理方法の紹介

こんにちは!AnotherBallのiOSチームです。

以前の記事では、モバイルチームがAvvyのマルチリポジトリ構成を紹介しました。Kotlin Multiplatform (KMP) と Unity as a Library (UaaL)のビルド、配布、そして5つのリポジトリ間の連携について、全体像をお伝えしました。

Avvyの技術面で面白いところはUaaLとNativeの自然な組み込みにあると思います。今回の記事では、iOSに焦点を絞って、SwiftUIアプリの中にどのようにUaaLの画面を管理しているかを解説します。

前提:UnityとNativeの役割

Avvyの設計思想として、Unityは2Dアバターに関する機能のみを持つようにしています。Unityはアバターの描画やアバターのカスタマイゼーション(着せ替え)のUIを担っています。それ以外の機能やUIはNative側で実装して、Nativeの機能を最大限に使えるようにし、配信アプリらしい体験を提供できるように設計しました。

UaaLの制約:インスタンスは1つだけ

UaaLを使う上で大きな制約があります。Unityランタイムの複数インスタンスの読み込みはサポートされていないため、UaaLのViewは画面上に1つしか表示できません。同時に2つ表示しようとすると、片方が描画されません。

Avvyでは配信画面、アバターホーム、ガチャなど、複数の画面でアバターを表示しています。そのため、画面遷移のたびにUaaLのViewを付け替え、二つ同時に画面に存在しないようにする必要があります。しかし、各画面でこのライフサイクルを意識するのは煩雑で、意図しない不具合の可能性が高まります。

そこで、SwiftUIで専用View「UnityView」を作成して、管理を集約し、各画面からは通常のViewと同じ感覚で使えるようにしました。

配信画面の構成

例として配信画面を取り上げて解説します。配信画面ではUnityは一番下のレイヤーでアバターのレンダリングのみを担当し、NativeUIがその上にオーバーレイされています。

グレー色はUnityのアバターの描画領域で、黄色がNativeのオーバーレイです。Unityの領域はAvvyのiOSアプリではUnityViewと名付けています。SwiftUI側から見ると、通常のViewと同じようになります:

1
2
3
4
5
6
7
8
9
10
UnityView(displayType: .liveStream) // UnityでロードしたいSceneを指定
.frame(maxWidth: .infinity, maxHeight: .infinity)
.ignoresSafeArea() // セーフエリアを含めて全画面描画
.overlay {
if viewModel.isSceneLoading {
LoadingOverlay() // ローディングインジケーター
} else {
overlayContent // Nativeボタンやコメントリストなど
}
}

Unityのライフサイクルを一切気にせず、画面を開く・閉じるだけでアバターの表示・非表示が切り替わります。

UnityViewの実装

UnityViewUIViewControllerRepresentableで、内部にUnityViewControllerを持っています。なぜUIKitのViewControllerが必要かというと、UnityFrameworkが提供する描画ViewがUIKitのUIViewだからです。

例えばアバターホーム画面から配信画面をモーダルで表示した場合、UnityのViewを自動で最前の画面に付け替える必要があります。UnityViewControllerviewWillAppear/viewWillDisappearのライフサイクルでこれを実現しています:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public struct UnityView: UIViewControllerRepresentable {
public let displayType: DisplayType

public func makeUIViewController(context: Context) -> UnityViewController {
return UnityViewController(displayType: displayType)
}
}

public final class UnityViewController: UIViewController {
// UaaLが提供するUnityのView。記事用に簡略化しています。
// シングルトンなので、全画面で同じインスタンスを使い回しています。
private var unityView: UIView = UnityFramework.shared.rootView

public override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
addUnityView() // Viewの追加
}

public override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
removeUnityView() // Viewの削除
}

private func addUnityView() {
view.insertSubview(unityView, at: 0) // UnityのViewを最下層に追加
unityView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
unityView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
unityView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
unityView.topAnchor.constraint(equalTo: view.topAnchor),
unityView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
}

private func removeUnityView() {
unityView.removeFromSuperview()
}
}

画面が表示されるとき(viewWillAppear)にUnityのViewを追加し、非表示になるとき(viewWillDisappear)にViewを外します。極めてシンプルな実装ですが、これだけで UnityのUIViewが一つのみ存在することを担保できます。

なお、View入れ替え処理と同時に、AvvyアプリではUnityの一時停止、再開も行なって、バッテリー消費とリソース負荷を抑えています。

まとめ

UaaLをNativeアプリに組み込む際、「同時に表示できるViewは1つだけ」という制約がありますが、UIViewControllerのライフサイクルを使ってViewの付け替えとリソース管理を自動化し、SwiftUIの UnityView としてラップすることで、それらを意識することなくアバター表示ができるようにしました。この構成により、AvvyはアバターアプリでありながらNativeアプリとしての操作感を保てています。

次回は、本記事に登場した「DisplayType」を使ってUnityに指定のSceneをロードさせる仕組みなど、NativeとUnityがどのようにコミュニケーションをしているかについてご紹介したいと思います。

We’re Hiring

AnotherBallでは、テスタブルでメンテナブルなアーキテクチャを大切にしており、同じ志を持つエンジニアを常に探しています。こうした仕事に興味があれば、ぜひお話しましょう!

AnotherBall Careers

チームの停滞と優先順位ズレに気づくカンバンをAIで作る ── Chrome拡張とAIコーディングによる可視化の実践

こんにちは!AnotherBallでテックリード兼Avvy事業部技術責任者をやっている @fortkle です。

今回は、スクラムでのタスク管理をより良くするためにChrome拡張を自作した話を書こうと思います。

自由度の高かった物理ホワイトボードのカンバン

現在、Avvyの開発チームでは開発手法としてスクラムを採用しており、日々の透明性と検査・適応を促すためにカンバンボードを活用しています。

カンバンを語るとき、今も頭に浮かぶのが以前の職場で使っていた物理のホワイトボードとポストイットです。

やっていたことはシンプルで、

  • ホワイトボードにマーカーでレーンを引く(Todo / In Progress / In Review / Done)
  • 上から優先順位順にカードを並べる
  • 左端にIssue(親)、その右にSub-issue(子)を横展開

これだけで「今スプリントで何をどの順番でやるか」がひと目でわかりました。

まずはシンプルなルールで始めましたが、スクラムを進めていくうちにカンバンの形も少しずつ変わっていきました。

例えば、メンバーのSlackアイコンを印刷してマグネットに貼り、担当しているタスクの上に貼ってアサインを可視化したり、「このレーンは最大1枚まで」とマーカーで書いてWIP制限したり。

カンバンガイドにも「どのようにフローの透明性を確保するかは、カンバンシステムメンバーの想像力以外に制限を受けない」と書かれていますが、物理のホワイトボードはまさにそれが実現できるツールでした。

ちなみに、このあたりの物理カンバンの活用アイデアはアジャイルコーチの道具箱 – 見える化実例集が非常に参考になるのでおすすめです。

Linearに移行して「あの感じ」を再現したくなった

今のAvvy開発チームは地方や海外からリモートワークで働くメンバーもいるため、物理のホワイトボードとあまり相性がよくありません。そのためカンバンは物理カンバンではなくLinearというデジタルツールを使っています。

LinearにはBoard Viewがあり、カード管理はできるのですが、使ってみるとホワイトボード時代の感覚とは違う部分がいくつかありました。

担当者ごとにグルーピングして表示する例(表示されているデータは全て架空のものです)

  • IssueとSub-issueの親子構造をBoard上で理想の形に配置できない(grouping/sub-groupingなどを駆使すればできなくはないがベストではない)
  • デフォルトのUIだと1画面に表示できる内容に限界があり、スプリント全体を把握しづらい
  • 停滞しているカードが一目でわかりづらい
  • WIPを視覚的に制限できない

「できなくはないけど、あの感じが出ない」という状態が続いていました。

Chrome拡張でカスタムビューを乗せた

そこで試したのが、「Chrome拡張でLinearにカスタムViewをオーバーレイする」という方法です。

Linear APIでIssueとSub-issueの情報、ステータスの変更履歴などが取得できるので、それをもとにホワイトボード時代のレイアウトをブラウザ上で再現しました。

物理カンバンを再現したカスタムView(表示されているデータは全て架空のものです)

特徴としては以下の通りです。

IssueとSub-issueの親子構造を展開
IssueカードからSub-issueがBoard上で展開されるので、全体像が把握しやすくなりました。スプリント全体を1画面で見渡せるのも大きなポイントです。

優先順位順の縦並び
スプリント内のタスクが優先順位順に上から並びます。スプリントが進むにつれてカードは / を描くように右上へ移動していくので、優先順位の低いカードが先に動いているときにすぐ気づけます。

差し込みタスクへの対応
優先順位が可視化されているので、急な追加タスクが入ったときに「優先順位の低いこれを諦めて追加タスクを入れよう」という判断がしやすくなりました。

AIに頼むことでやりたいことが5分で実現できる

技術的には以前から可能でしたが、ここにAIコーディングの進化が加わるとさらに自由度が増します。どういうことかと言うと、AIコーディングを使えば「ちょっとこう変えたい」がすぐ実現できるということです。具体例をあげていくつか工夫した点をご紹介します。

停滞インジケーター

「In Progressで止まっているメンバーがいても、ボードを見ただけではなかなか気づけない」という問題がありました。例えば、物理カンバン時代ならポストイットに正の字で経過日数を表現したりして簡単に可視化できていた部分です。

これもAIに「レーンでの経過時間を可視化したい。担当者アイコンにインジケーターのように5分割したゲージを出し、1日経過するごとにそのゲージを埋めて最大5日まで停滞状況がわかるようにして」と伝え、以下のようなUIを作ってもらいました。ここまでものの数分です。

各カードの右上にある担当者アバターに5分割のリングインジケーターが付いて、In Progressに移動してから1日ごとにゲージが1つ埋まります。「このカード、もう5日止まってるな」とボードをひと目見れば全員が気づけるようになりました。

Linear APIがステータス変更の日時情報を持っているので、サーバーサイドに何も追加することなくクライアントサイドだけで実現できたのも良かったです。

絞り込み・カードへのフォーカス

毎朝のデイリースクラムで「今日やること」を1人ずつメンバーがチームに対して共有するとき、カンバン上のどこにその人のカードがあるか一目でわからない問題がありました。物理カンバン時代なら直接該当するカードを指差しすれば終わっていた部分です。

これもAIに「担当者でカードを絞り込んだ場合にその人の対象カードがどこにあるのか見失うときがある。絞り込んだときだけ、添付画像の位置に←→のようなボタンを置いてその人のカードに1つずつフォーカスする挙動にしたい。ブラウザのページ内検索(Ctrl+F)のようなイメージ。」と添付画像とともに伝えて以下のような機能ができました。

UIなど少しの微調整は行いましたが、あっという間にイメージ通りのものが実装できました。
また、絞り込みというと一般的に「選択したもの以外が表示されなくなる」という挙動が多いですが、今回は意図的に消すのではなく選択されたものをハイライトで目立たせる実装にしています。そのため、絞り込み中も他のタスクとの関係性を確認できる点も使いやすいポイントです。

使い始めて起きたチームの変化

このボードを使い始めてからチームで起きた変化をいくつか列挙すると以下の通りです。

  • 「いま着手しているタスクより、こちらのタスクの方が優先順位が高そうです!」という会話が増えた
    • 優先順位順に並んでいると、ズレに気づきやすくなります。
  • 停滞に早めに気づけるようになった
    • In Reviewで数日止まっているカードにも気づきやすくなり、「詰まってるなら手伝えるよ」という申し出が早めに出せるようになりました。
  • スプリントの取捨選択がしやすくなった
    • 急な追加タスクが入ったときの「これは今スプリントで諦めよう」という合意がしやすくなりました。

一方、デメリットとしてはChrome拡張であるため、スマホや専用デスクトップアプリからは使えない点は注意が必要です。また、社内限定であってもChromeウェブストアで配布する場合は審査に2〜3日かかるため、チームへの展開が遅くなる点も気になりました。

まだまだ使い始めたばかりですが、AIを活用することで理想的なカンバンを作成できる可能性を強く感じました。

チーム内でのChrome拡張の配布について

Chrome拡張はChromeウェブストアで配信しつつ、公開範囲を社内に限定することもできます。具体的な手順はこちらの記事が参考になりました。

Google グループのメンバーに配布する - Google

また、Chrome拡張の審査の提出はGitHub Actionsで自動化できるため、PRをマージすれば自動的に審査中になるようにChrome拡張のリポジトリを整備しています。

まとめ

「物理カンバンの自由度はデジタルツールでは再現できない」と半ば諦めていましたが、AIコーディングで状況が変わった実感があります。

「こう可視化したい」を自然言語で伝えたら数分で動くUIになる。ツールの制約に縛られず、チームが本当に必要な可視化を自分たちで作れるようになってきました。

今後もチームのおかれた状況とニーズに合わせてカンバンをカスタマイズし、育てていこうと思います。

We’re Hiring

AnotherBallでは、今回紹介したようにチームの課題を自分たちで発見し、解決していく文化を大切にしています。そんな環境でプロダクト開発に携わりたいエンジニアを募集しています。

AnotherBall Careers

ZendeskからChatwootへ ── AIを中心にCSフローを再構築

こんにちは!AnotherBallでAIOpsを担当しているFrancisです。AIを社内オペレーションに組み込み、現場の業務改善を進めています。この記事では、カスタマーサポートの問い合わせ対応をZendeskからChatwootへ移行し、初回返信時間を1,186分から1分未満まで短縮できた経緯とその仕組みについて解説します。

Chatwootへの移行

元々CSはZendeskを利用していました。ユーザーはアプリ内の「お問い合わせ」からZendeskのフォームを送信し、CS側はZendeskのチケット画面で対応する、という形です。CS対応の効率化のためにAIを活用したいと考えていましたが、Zendeskは高度なAIカスタマイズを想定しておらず、応答フローのカスタマイズにはさまざまな有料アドオンとひと工夫や小技が必要でした。問い合わせごとに返信のトーン、テンプレートの使用有無、エスカレーションフローなどを設定したかったのですが、Zendeskではやややりづらい部分がありました。

そこで選んだのはChatwootです。Chatwootのほうはそういうカスタマイズがはるかに楽にできます。さらにホスティングプランで運用コストを抑えられますし、将来的に必要になったらオープンソース版へ移行できる選択肢もあります。オープンソースである分、機能や外部サービスとの連携の挙動がわかりやすく、トラブルシュートもしやすいのも大きなメリットです。

課題: Chatwootはチャット中心、我々はフォーム中心

以前使っていたZendeskでは、フォームで問い合わせを受け付けていましたが、Chatwootはチャットを想定したCSツールです。ただ、ほとんどの問い合わせはリアルタイム対応が不要で、むしろ最初のメッセージでできるだけ詳しく状況を書いてもらった方が解決が早いので問い合わせ導線をフォームのままにしたいと考えていました。チャット形式だと情報が足りないまま送られやすく、確認のやり取りが増えてしまうことを懸念していました。

また、問い合わせ対応には端末モデル、OSバージョン、アプリバージョンなどの情報も必要です。既存のフォームではアプリ内から送信する際にこれらが自動入力されるため、ユーザーが手動で入力する必要はありません。しかし、チャットウィジェットでこれを確実に実現するのは容易ではありませんでした。そのため、フォームでの受付を維持しつつ、各問い合わせをChatwootのネイティブ会話に変換する仕組みが必要でした。

解決策: Google フォーム + スプレッドシート + Apps Script

完全に自分でコントロールできるスタックを選びました。

  1. Google フォーム — ユーザーが使うお問い合わせフォーム
  2. Google スプレッドシート — フォームの回答がここで自動的に蓄積される
  3. Google Apps Script — ChatwootとSlackへの連携を担う

フォームが送信されると、Apps Scriptが回答内容をGoogleのサポートメールボックスへメールとして送信します。Chatwootはそのメールボックスを読み込み、届いたメールから自動的に会話を作成します。その後はAPIで問い合わせカテゴリを示すラベルと連絡先の属性を設定します。

もう一個のスクリプトはWebhookハンドラーです。Chatwootは新しいメッセージごとにmessage_createdイベントを発火するので、Apps Scriptがそれを感知しメッセージをSlackに転送します。ユーザーからのメッセージが親スレッドを作り、エージェントとAIの返信はスレッド内に投稿されます。

CSの問い合わせフロー:Google Form → Chatwoot → Slack

すべての問い合わせがGoogle Sheetに蓄積されるのが、この仕組みの最大のメリットです。生データが常にスプレッドシートにあるため、週次のCSダイジェストも自動化できました。毎週金曜日、機能リクエストを含め、直近1週間の問い合わせをLLMで分類し、日英バイリンガルのサマリーをApps ScriptでSlackに自動投稿します。

その結果、チームの反応は早く、CSを超えたアイデアも出てきました。たとえばSNSの反応にも同じようなアプローチを適用し、ユーザーがアプリをどう体験しているのかをもっと深く把握しようというアイデアがありました。ユーザーの問い合わせにただ返信するだけではなく、きちんと理解しにいくという取り組みが始まりました。

Chatwootのプロンプトエンジニアリング

AIの返信を安定させるために、プロンプト設計で工夫した点をいくつか紹介します。

  • テンプレート構造を明示する。 人間のエージェントが使っている見出し、トーンのガイドライン、締めの定型まで、プロンプトにそのまま入れています。構造がはっきり書かれていると、モデルはかなり安定して従ってくれます。

    なお、プロンプト自体は日本語ではなく英語で書いています。日本語で返信する場合でも、指示は英語の方がモデルが安定して従うことがわかりました。

    1
    2
    3
    4
    5
    6
    Role: You write professional emails on behalf of [App] Support.
    Use a greeting, structured paragraphs, and a courteous closing.
    Insert TWO line breaks between paragraphs.

    Greeting: Address the user by username (e.g. "user_12345").
    If no identifier is available, use a neutral greeting.
  • 問い合わせ種別ごとのルーティング。 問い合わせカテゴリに応じてプロンプトを切り替えています。ボットがメッセージ内容から種別を判定して適切なプロンプトを選ぶので、手動でタグ付けすたりする必要はありません。

  • エスカレーション・ロジック。 ボットが自信を持てないときに人間が対応するようにします。

結果

2月16日にGoogle フォームとChatwootを用いたこの新しい仕組みへ切り替えました。

指標 Zendesk平均(1/15〜2/15) Chatwoot平均(2/16〜2/26)
初回返信時間 1,186分(19.8時間) 約1分
解決時間 289.7時間(12.1日) 30時間(1.25日)

Zendeskの平均値はいくつかの外れ値によって引き上げられていますが、中央値は299分 / 172.8時間で、それでもChatwootの平均値を大きく上回っています。まだChatwootの10日分のデータしかないのですが、1ヶ月分のデータが揃ったら改めて見直す予定です。

初回返信が約1分なのは、会話が作成された瞬間に時間帯に関係なくAIが自動返信しているためです。人間のエージェントが対応する場合の平均返信時間は6時間46分です。

データがすべてスプレッドシートにまとまっているので、データ分析がとても楽です。問い合わせ件数の推移を追ったり、最近増え始めた不具合の兆しを早めに拾ったり、週次のバイリンガルCSダイジェストを自動配信したりすることができます。追加の有料分析機能に頼らず、必要なことが回るようになりました。

セットアップにはそれなりに手間もコストもかかりましたが、今は明らかに以前より良い状態です。自分たちでコントロールできるスタックがあり、実運用で動くAI連携があり、すぐに意思決定につなげられるデータも手に入れています。学びを一つ挙げるなら、データと連携レイヤーを自分たちで握ることです。このトレードオフが合うかどうかはチーム次第だと思いますが、AIに本気で投資して高速に改善をしたい我々のチームにとっては、よりオープンで容易にコントロールできるこのスタックがピッタリです。

採用情報

AnotherBallはエンタメとテクノロジーの交差点に立つ会社です。人々が愛するコンテンツやコミュニティとのつながりを深めるプロダクトを作りながら、その実現のためにAIをフルスタックで活用しています。

もしこういう環境に少しでも興味を持っていただけたら、ぜひ採用情報も見てみてください。

採用情報はこちら

KMP × Unity UaaL のモバイルアプリ開発 ── マルチリポジトリ構成と自動化の話

こんにちは!AnotherBall モバイルエンジニアチームです。

私たちが開発している「Avvy」は、ビジネスロジックをiOS/Androidで共通化する KMP(Kotlin Multiplatform) と、Unity製の2Dアバター描画機能をネイティブアプリへ組み込む Unity as a Library(UaaL) を組み合わせた、ちょっと複雑な構成のアプリです。

この記事では、5つのリポジトリをどう連携させているか、そしてGitHub Actionsでどこまで自動化しているかを紹介します。

リポジトリ構成

ビルドの複雑化やチーム間の依存関係といった課題を避けるため、リポジトリを機能ごとに分割しています。

リポジトリ 役割 成果物
shared-kmm ビジネスロジック AAR / XCFramework(KMPライブラリ)
unity-module 2Dアバター描画 Android / iOS向けUaaLライブラリ
android-app Androidアプリ本体 APK / AAB
ios-app iOSアプリ本体 IPA
unity-spm Unity XCFrameworkのSPM配布用 Swift Package

各リポジトリは、ビルド済みのライブラリ(AndroidはAAR、iOSはXCFramework)を通じて連携します。こうすることで、各チームが独立して開発を進められます。

Unityモジュールの役割

Unityモジュールは、アバターの表示とリアルタイム制御を担当しています。

  • アバター表示: 2Dアニメーションでアバターを描画
  • フェイストラッキング: カメラで顔の動きを検出し、アバターに反映
  • カスタマイズ: 衣装やアクセサリーの着せ替え

ネイティブアプリとの通信には工夫が必要です。フェイストラッキングは毎秒60回データを送るため、通常のブリッジでは遅延が発生します。iOSではポインタ経由の直接アクセス、Androidではメモリマップドファイルを使って高速にデータをやり取りしています。

自動化の仕組み

GitHub Actionsを使って、リポジトリ間の連携をほぼ自動化しています。月間約1,200件のPRが自動で処理されています(2025年12月実績)。

サーバーAPIの変更を反映

サーバーのAPI定義ファイル(OpenAPI)が更新されると、KMPリポジトリに更新PRが自動で作成されます。

KMPライブラリの更新

KMPライブラリのリリース → 各アプリリポジトリへの更新PRの作成まで、すべて自動です。

流れはシンプルで、「Publish → Trigger → Update」の3ステップです。

  1. shared-kmmでリリースがトリガーされると、GitHub Packagesに公開
  2. 公開が成功したら、gh workflow runで各アプリリポジトリの更新ワークフローを起動
  3. 各アプリでバージョンを更新したPRが自動作成される
1
2
3
4
5
6
7
8
9
# KMPリリース時(抜粋)
- name: Publish to GitHub Packages
run: ./gradlew publish

- name: Trigger Android update
run: |
gh workflow run update-kmm-version.yml \
--repo AnotherBall/android-app \
--field version=${{ env.VERSION }}
1
2
3
4
5
6
7
8
9
10
11
# Androidアプリ側の更新ワークフロー(抜粋)
- name: Update version in libs.versions.toml
run: |
sed -i "s/kmm = \".*\"/kmm = \"${{ inputs.version }}\"/" \
gradle/libs.versions.toml

- name: Create Pull Request
uses: peter-evans/create-pull-request@v5
with:
title: "Update KMP to ${{ inputs.version }}"
branch: "auto/kmm-${{ inputs.version }}"

Unityライブラリの配布

UnityモジュールをiOSアプリで使うため、SPM(Swift Package Manager)形式で配布しています。

  1. Unity XCFramework をビルドし、GitHub Release にアップロード
  2. ファイルのハッシュ値を計算
  3. Package.swift を自動生成(ダウンロードURLとハッシュ値を埋め込み)
  4. 配布用リポジトリ(unity-spm)にPRを作成

unity-moduleリポジトリでの直接配布も可能ですが、Xcodeの仕様上、SPMパッケージ取得時にリポジトリ全体をダウンロードしてしまいます。Package.swiftのみを格納するunity-spmを用意することで、ダウンロードを高速化しています。

ハッシュ値を埋め込むことで、iOSアプリ側でファイルが壊れていないか確認できます。

リリースブランチの自動PR

release/* ブランチにpushされると、複数のマージPRが自動で作成されます。

  • release/2.10.0main へのPR(本番リリース用)
  • release/2.10.0release/2.11.0 へのPR(バグ修正を次バージョンに伝搬)

バージョン番号を比較して、適切なマージ先を自動で判定しています。これにより、マージ漏れを防げます。

Package.resolved のコンフリクト解決(iOS)

複数のKMM/UaaL更新PRが同時に存在すると、Package.resolved ファイルで競合が発生します。iOSリポジトリでは、この問題を自動解決するワークフローを用意しています。

トリガー: release/* ブランチへのpush

処理の流れ:

  1. GitHub APIでリリースブランチ宛のオープンPRを取得
  2. タイトルが chore: update KMM or chore: update UaaL で始まるPRを絞り込み
  3. マージできるかどうかをチェックし、競合中のPRを特定
  4. 各PRの競合を解決
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# baseブランチをマージ試行
if git merge "origin/$BASE_REF" --no-edit; then
echo "Merge succeeded"
else
# 競合時: PR側のPackage.resolvedを一旦採用
git checkout --ours Package.resolved
git add Package.resolved
git merge --continue
fi

# SPMの依存関係を再解決(baseの変更も反映される)
make resolve-package-dependencies

git commit -m "chore: resolve Package.resolved conflict"
git push

ポイントは make resolve-package-dependencies(内部で xcodebuild -resolvePackageDependencies を実行)で、マージ先ブランチの変更も含めて依存関係を再解決している点です。複数のPRがある場合は並列で処理します。

今後の課題

  • CI/CD実行時間: Gradle/Xcodebuildで40分以上かかることがあり、キャッシュ改善を検討中
  • ワークフローの重複: 似たような処理が複数ファイルにあるので、共通部品として切り出したい
  • 更新PRの自動マージ: 現在はPR作成まで。テストが通れば自動マージまでやりたい

まとめ

KMPとUnity UaaLを組み合わせた複雑な構成でも、リポジトリを分けて成果物経由で連携すれば、各チームが独立して動けます。自動化は一度作って終わりではなく、運用しながら改善していくものだと実感しています。

We’re Hiring

AnotherBallでは、Kotlin/Swift/Unity/AIを活用したアプリ開発に興味のあるモバイルエンジニアを募集しています!
KMPなどの新しい技術を取り入れながら、プロダクトを一緒に成長させていく仲間を探しておりますので、ぜひ下記のリンクから応募いただけますと嬉しいです!

Kotlin Fest 2025 参加レポート ── AI・KMP・開発体験の最前線

こんにちは!
AnotherBall MobileチームのRIO(@rioX432)とApippo(@A5th_Faris)です。

私たちは普段、KMMとCompose Multiplatformを使った Avvy のAndroid/iOS開発を担当しています。日々AIを活用しながらネイティブコードを書いているので、今回のKotlin Fest 2025はまさに私たちの業務に直結するテーマが盛りだくさんでした。

2025年11月1日に開催された Kotlin Fest 2025 に参加してきました!

今年は3部屋での発表が行われ、過去最大の協賛企業数と発表数を記録。弊社AnotherBallもシルバースポンサーとして協賛しております!

会場全体が熱気に包まれた、まさに “祭り” のような一日でした。

Kotlin Festとは

Kotlin Fest は「Kotlinを愛でる」をテーマに、Kotlinやその周辺技術の知見共有と交流を目的とした国内最大のKotlinコミュニティカンファレンスです。

今年の会場は 東京コンファレンスセンター品川

企業ブースやセッション後の交流スペースも盛況で、実装や設計の具体的な話が飛び交っていました。特に印象的だったのは、サーバーサイドKotlinを採用している企業が確実に増えていたこと

AndroidやKMPだけでなく、Kotlinが「汎用言語」として広がっている実感がありました。

聴講したセッションまとめ

オープニングセッション

「AIがコードを書く時代に、Kotlin開発者が果たす役割」
長澤太郎、森篤史、玉木英嗣 / Kotlin Fest オーガナイザー

AIがコードを書く時代でも、開発者は「レビューと判断の責任者」であり続ける。
Kotlinがこれからの時代に最も適した言語である理由が紹介されました。

  • シンプルで表現力の高い構文
  • JetBrains製IDEによるAI支援エコシステム
  • 一貫性のある学習データ

“AIに仕事を奪われる”のではなく、”AIと共に設計を進化させる”。
その方向性をKotlinコミュニティ全体で共有できたオープニングでした。

【招待セッション】Kotlinを支える技術:言語設計と縁の下の力持ち

Yan Zhulanow / Kotlin Team

Kotlin 2.3 の新機能や Build Tools API の開発経緯が語られました。

特に Build Tools API は、KMMを採用しているAnotherBallでも検討すべき内容
モジュールごとのインクリメンタルビルドやKMP対応が進み、ビルド速度や開発体験が大きく改善しそうです。

Kotlinの進化は「新しさ」よりも「安定と一貫性」を重んじている。

Kotlin Compiler Pluginで実現するCustom String Interpolation

be-hase さん

Kotlin Compiler Plugin の内部構造を理解し、文字列補間をカスタマイズする実装事例。IR(中間表現)拡張を用いたコード生成の仕組みが丁寧に解説されました。言語を”使う”側ではなく、”広げる”側の視点が得られるセッションでした。

せめて、ネイティブらしく - マルチプラットフォームと撤退戦略

RyuNen344 さん

マルチプラットフォーム導入時だけでなく、撤退の痛みをどう局所化するか をテーマにした現実的なセッション。Swift Export や Kotlin/Native の制約を踏まえつつ、段階的な置き換え戦略が紹介されました。Kotlin Multiplatformを「一気に導入しない、きれいに戻せるように設計する、特にInterfaceを綺麗に定義する」思想。Avvyのようなハイブリッド構成のアプリにも重要な視点でした。

Kotlinで実装するCPU/GPU「協調的」パフォーマンス管理

matuyuhi さん

Android Dynamic Performance Framework (ADPF) をKotlinで安全に扱う手法を解説。
Flowによる非同期データ処理、sealed classでの型安全設計、DSLによる制御など、Kotlinらしいアプローチ。Avvyでも 即検討すべき内容。発熱対策だけでなく、低スペック端末での顔検出推論の高速化にも有効だと感じました。

Inside of Swift Export - KotlinとSwiftを繋ぐ新しい仕組み

giginet さん

Swift Export により、Swiftから直接Kotlinコードを呼び出せるようになったという解説。ObjCブリッジを経由しないため、iOS側でのAPI体験が大幅に改善。CMPやKMMの採用・撤退どちらにおいても意味を持つアップデートでした。まだまだプロダクションでは使えない段階ですが、今の内に弊社もObjCからの移行の準備していくのが良さそうだと思いました。

Rewind & Replay: Kotlin 2.2 が変える Coroutine デバッグ最前線

daasuu さん

Kotlin 2.2 + IntelliJ IDEA 2025.1 によるデバッグ改善の紹介。ローカル変数保持、スタック復元、ステップ実行などが強化されました。確実に使いやすくなる改善で、日々の開発効率をじわじわ上げてくれそうなアップデートでした。

フルKotlinで作る! MCPサーバー、AIエージェント、UIまで一気通貫

Shuzo Takahashi さん

Kotlin MCP SDK を使い、KotlinだけでMCPサーバー・AIエージェント・UIまで実装するアプローチを紹介。発表では FigmaとMCPサーバーを連携させた事例 もあり、非常に刺激的でした。Avvyでもチャレンジすべき内容。特にUI生成支援やデザイン自動連携の部分に応用できると感じました。

KoogではじめるAIエージェント開発

hiro さん

JetBrains発のAIエージェント構築ライブラリ「Koog」 の詳細セッション。Langfuse連携、DSL構築、並列実行など、本格的なAIワークフロー設計が可能。これは 即座に取り組みたいテーマ

「AIで何をやらせるか」という発想に転換し、UIコード生成支援から、全社OKRの進捗管理・PdMの意思決定支援などに活かしたいと考えました。

AIとの協業で実現!レガシーコードをKotlinらしく生まれ変わらせる実践ガイド

nishimy432 さん

JavaコードをAIでKotlin化する際に「Kotlinらしさ」をプロンプトで教え込む手法を紹介。data class や scope関数、sealed class、null安全など、チームの文化をAIに反映させるという視点が印象的でした。コードスタイルをAIに伝えるという取り組みは、AnotherBallでも活かせそうです。

全体を通して感じたこと

Kotlin Fest 2025は、AI・マルチプラットフォーム・開発体験の進化という三本柱で構成されていました。

  • KotlinはAIを動かす側の言語へ
  • Compose MultiplatformやKMMは「撤退を設計する技術」へ
  • Kotlin開発は「安全と表現力の両立」へ

AnotherBallで開発中の Avvy でも、ネイティブUIUXの最適化を重視しながらKMMによるビジネスロジック共通化を実現し、Unityを使ったFaceTrackingなど非常にチャレンジングな構成を採用しています。さらに、AIを活用したネイティブコード開発も積極的に取り入れており、今回のセッションで紹介されていた技術の多くがまさに私たちの開発現場に直結する内容でした。

私たちは改めて、フルCompose構成で最新アーキテクチャに取り組める環境の価値を再認識しました。

さいごに

Kotlin Fest 2025 は、Kotlinコミュニティの厚みを実感できるイベントでした。登壇者・参加者ともに、AIやマルチプラットフォームを「現場でどう使うか」を語る姿勢が印象的。AnotherBallとしても、Kotlin×AIの領域でさらに挑戦を続けていきます。

弊社からの参加者

Have a nice Kotlin!

We’re Hiring

AnotherBallでは、Kotlin/Unity/AIを活用したアプリ開発に興味のあるエンジニアを募集しています。
KMMやCompose Multiplatformなどの新しい技術を取り入れながら、
プロダクトを一緒に成長させていく仲間を探しています。

AnotherBall 採用情報はこちら

AI活用が進む“仕掛け” ── 会社をBAKUSOKU化した4つの取り組み

こんにちは、AnotherBall CTOの @tatsushim です。年末なので、今年の振り返りとして4月~6月にAIで会社をBakusoku化した取り組みをご紹介します。
今回の取り組みを始める前は「一部の人だけがAIを使う」状態で全社的な活用には至っていませんでした。Google FormでAI活用についてアンケートをとったところ

  • ChatGPTは無料版を使い続けている
  • ChatGPT以外のAIツールは触ったことがない

という方もいる状況で、少しショックでした。
しかし、AIによる生産性向上は自明です。全社をあげて施策を行った結果

  • 「AI前提」で業務に取り組むようになった
  • 「AIができること・できないこと」の感覚を持てるようになった
  • 「まずAIでやってみよう」という言葉が自然に出るようになった

といった声をもらうまで組織をアップデートできました。特に、非エンジニア(マーケティングやHR、バックオフィスなど)のメンバーの生産性向上は非常に大きなものでした。

以下では具体的に私たちが実践したことをご紹介します。

BAKUSOKU化させた具体的な取り組み内容

1. スローガンの設定 ── Bakusoku AI 10x

AI活用が遅れることは、スピードが命のスタートアップにとって大きなリスクです。
そこで雰囲気を一気に変えるために、「AIの活用によって10倍のチャレンジをしよう」というコンセプトで Bakusoku AI 10x というスローガンを掲げました。 (Yahoo! JAPANが過去に掲げていた「爆速経営」からのオマージュです)

単なるスローガンのように思えるかもしれませんが、明示したことで「中途半端な取り組みではなく、全社としてコミットする」という強いメッセージを伝えることができました。

2. AIについて話す場づくり

スローガン掲げたと同時に、社内の定例活動の中でAIについて話す場を設けていきました。上手くAIを活用した事例を社内LTという形で紹介してもらったり、特定のツールのワークショップを開いたりしました。

中でも特に効果があったのは、AI Mingle という取り組みです。
朝礼のタイミングで2〜3人のグループをつくり、前日のAI活用を2分でシェアするというものです。
「AI Mingleでシェアしたい(逆に言えば話すことがなくなるのは避けたい)」という気持ちが、日々AIを活用する習慣づくりに自然とつながりました。

そしてこの施策は前述のスローガンと強いつながりがあります。AI活用をシェアしてくれたメンバーに「それ、BAKUSOKUじゃん!」と言える(言われる)場をつくることでスローガンもより浸透していきます。

また、Slackに #random-ai-lab チャンネルをつくり、リモートメンバーも含めて非同期で日々のAI活用を共有しました。

3. AI活用を称賛

メンバーのAI活用を、トップダウンと横のつながりの両面から称賛できないかと考えました。
そこで、経営陣は毎日1件投稿をピックアップして称賛し、組織全体のAI利用を後押ししました。

pick up事例。弊社は日英が公用語です。

一方で、AIを活用した人に「Bakusoku AI 10x」と書かれたシールを配布し、集めて競い合うAI HEROsという仕組みを導入しました。 従業員同士で誰かにシールをあげたり受け取ったりできるため、

  • 「その活用方法はすごい!Bakusokuだ!」と称賛される体験
  • ゲーム感覚でシールを集める楽しさ

が組み合わさり、モチベーションを高める仕掛けになりました。

4. 実務を個別撃破

ここまでの施策でAI活用が自然に進む人もいましたが、バックオフィスやマーケティングなど、分野によっては活用方法に悩むこともありました。

そこで、AI活用が得意なメンバーが一緒に課題解決に並走することにしました。

例えば、Social Mediaチームではコンテンツのプランニング、構成案出し、投稿後のデータ分析を、それぞれ属人的に対応していました。 そこで担当者がCursorを使ったワークフローを共に構築し、企画〜分析までを一連の流れとして回せるようになりました。
その結果、企画工数の短縮と分析の粒度向上を実現できました。一見、愚直でスケールしない施策に見えますが上手く行く例が出るとそれを横で真似たり、お互いが教え合ったりということが生まれるので、やる価値は大いに合ったと感じています。

定量・定性での評価

「AI活用が進んでいる」ことを定量的に評価するのは難しいですが、私たちは四半期で次の指標を設定しました。

  1. 全メンバー(100%)がAI関連の発信(LTや #random-ai-lab への投稿)を経験すること
  2. Slackの #random-ai-lab チャンネルに毎日5件以上の投稿が継続的に行われること

1を設定した理由は、私の中で「全社でAI活用を浸透させた」と言えるためには、全員が発信者になる必要があると考えたためです。また、メンバーからの自発的な投稿が生まれることも重要だと感じたため、2を設定しました。

結果として、1と2の両方を達成し、20営業日で合計150件(1日あたり7.5件)の投稿が生まれました。

さらに、定性的な変化としてメンバーのAIに対する意識も大きく変わりました。
冒頭でお伝えした通り、四半期終わりには

  • 「AI前提」で業務に取り組むようになった
  • 「AIができること・できないこと」の感覚を持てるようになった
  • 「まずAIでやってみよう」という言葉が自然に出るようになった

といった声をもらうことができました。
現在は部署ごとに具体的なワークフロー改善も進み、AI活用が「個人の工夫」から「組織の文化」へと進化していきました。

プロジェクトに割いたリソース

この取り組みは私と組織開発担当者の2人で推進しましたが、経営層のコミットメントと熱量のあるメンバーがいたからこそ実現できました。リーダー層が「まず踊っている人」としてAIに熱狂し、「二人目のダンサー」を施策や個別アプローチで生み出すことで、周囲も自然に追従するようになりました。

AIツールと予算の策定

弊社がチームメンバーに利用補助をしている代表的なツールの一部をご紹介します。

  • Canva
  • ChatGPT (Codex)
  • Claude (Claude Code)
  • Cursor
  • Gemini
  • Grok
  • Manus
  • Notion AI

この中にはメンバーからのボトムアップで採用したものもあります。ツールについて提案があったものは、まずは使いたいメンバーに会社が補助を出す形で試してもらい、良さそうであれば全体へ展開する形をとっているため、新しいツールを使ってみようというハードルをできるだけ下げています。
予算についてはケースバイケースの判断で、明確に最初から確保しているわけではありません。
また、年間契約はしないようにしています。新しいモデルやツールが日々更新されているので、ベストなツールも日々変わっていくからです。

まとめ

AI活用は「制度」や「ツール導入」だけでは文化になりません。

  1. 全社を巻き込むスローガンを設定
  2. AIについて話す場づくり
  3. AI活用を称賛
  4. 実務を個別撃破

こうした取り組みを日々積み重ねていくことで、AIが当たり前に使われる組織へと着実に近づいていきます。

さいごに

本投稿では今年前半に取り組んだAI活用が進む仕掛けについて共有させていただきました。夏から現在にかけて取り組んでいるAI活用はまた別の機会にご紹介したいと思います。
AnotherBallでは最新技術を活用し、日本が誇るエンタメ文化を軸に世界へ挑戦したい方を募集しております。絶賛採用中ですので、ぜひこちらを覗いてください。日本にとどまらず、世界で勝つプロダクトを一緒に創りましょう!以上、 @tatsushim がお送りしました!