「概要設計.md」ができあがったので、GitHub Copilot を使って実装をしていきます。Android Studio を立ち上げて Kotlin のプロジェクトをひな型として作成した後に、AI コーディングを進めるとよいです。
たぶん、Claude Code などのバッチ型はプロジェクト作成まで skills などで用意されているのですが、Web アプリ以外はちょっと難しいか…という感じで、Copilot の場合はよく失敗したので、手作業でプロジェクトだけは作っておきます。
ブランチを切る
AI ペアプロをする場合には、この時点でブランチを作成しておきます。
Claude Code の場合は自動でブランチを作ってくれるのですが、AI ペアプロの場合は、人間のほうで適当なタイミングで作成しておくとよいです。いつも通り、タスクやチケットを単位で作るような感じでよいです。
最初の AI コーディング
- 概要設計.md に従って src/copilot に実装して。
Claude Code の場合も試したいので GitHub Copilot では src/copilot に実装していきます。

ファイルを見ている限り、なんとか AI コーディングが終わった感じですが、残念ながらそのままでは動きません。
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.BLE5chat">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.BLE5chat">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
- AndroidManifest.xml に BLE を利用する権限を追加して。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-feature
android:name="android.hardware.bluetooth_le"
android:required="true" />
<uses-permission
android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission
android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
<uses-permission
android:name="android.permission.ACCESS_FINE_LOCATION"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
BLE の権限の設定はできたとして、Android Studio でビルド&実機で実行してみます。

これを見ると、
- ステータスバーが重なってしまっている
- 入力画面に「Bluetooth をオンにしてください」と表示されている
初動までを確認する
- 実機で BLE を ON にしても「Bluetooth をオンにしてください」がでます。
調査して。

結果として以下が追加で実装されます。
// Bluetooth アダプタ状態監視用 BroadcastReceiver
private val bleStateReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (intent?.action != BluetoothAdapter.ACTION_STATE_CHANGED) return
val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)
AppLogger.debug("BLE adapter state changed: $state")
lifecycleScope.launch {
when (state) {
BluetoothAdapter.STATE_ON -> {
AppLogger.info("Bluetooth turned ON")
_uiState.value = _uiState.value.copy(
canSend = true,
isScanning = true,
errorMessage = null
)
}
BluetoothAdapter.STATE_TURNING_OFF,
BluetoothAdapter.STATE_OFF -> {
AppLogger.info("Bluetooth turned OFF (state=$state)")
_uiEffects.emit(ChatUiEffect.NotifyBleDisabled)
}
}
}
}
}
これで一応入力ができるようになるのですが、実は「概要設計.md」には、BleChatService の責務として書いてあります。
#### BleChatService - 責務 - `BleAdvertiserManager` と `BleScannerManager` を統合 - BLE 権限・Adapter 状態を監視 - アプリの開始/停止に追従して送受信を制御 - `BroadcastReceiver` で `ACTION_STATE_CHANGED` を受信し、`STATE_TURNING_OFF` / `STATE_OFF` を検知したら送受信を即時中断して `ErrorHandler` へ委譲する - BLE OFF 検知時は `ChatUiState.canSend = false` と `ChatUiEffect.ShowToast` を上位へ通知する - 主なメソッド - `start()` - `stop()` - `send(chatMessage: ChatMessage)` - `incomingMessages(): Flow<ChatMessage>` - `onBleAdapterStateChanged(state: Int)` ※ BroadcastReceiver から呼ばれる内部ハンドラ
元のコード内にも BleChatService.start(), BleChatService.stop() として使われているので、これ活かす形に書き変えて貰います。
private fun handleStartChat() {
lifecycleScope.launch {
try {
AppLogger.info("Starting chat")
_uiState.value = _uiState.value.copy(canSend = true, isScanning = true)
// TODO: BleChatService.start()
} catch (e: Exception) {
AppLogger.error("Failed to start chat", e)
_uiEffects.emit(ChatUiEffect.ShowToast("チャット開始に失敗しました"))
}
}
}
private fun handleStopChat() {
lifecycleScope.launch {
try {
AppLogger.info("Stopping chat")
_uiState.value = _uiState.value.copy(canSend = false, isScanning = false)
// TODO: BleChatService.stop()
} catch (e: Exception) {
AppLogger.error("Failed to stop chat", e)
_uiEffects.emit(ChatUiEffect.ShowToast("チャット停止に失敗しました"))
}
}
}
実装を設計にあわせる
- BroadcastReceiver による動的監視の部分を、概要設計.md 通りに、BleChatService を実装して利用できますか?

これで BleChatServiceImpl.kt が実装されます。このあたり、GitHub Copilot が設計を忘れいるのか、それとも Claude Code ならば大丈夫なのか、という問題がありますが、少なくとも「必ずしも設計通りにコーディングをしないこともある」というのが重要です。
今後はどうなるのかわかりませんが、なんらかの形で「設計書通りにコーディングする」というスタイルを促すようなプロンプトの工夫が必要になると思われます。
あるいは、設計書通りにコーディングされているかをチェックするテストコードを追加することになります。
受信状態を実機で確認する
受信状態を実機で確認してみますが、受信人数が 0 人のままになります。かつ、メッセージを送信しても受信ができていません。
- 複数の端末を起動しても 0 人のままになります。

この手の実機動作確認が AI コーディングの一番の難所になります。
AI としては直しているつもりなのですが、まだ動かないようなので、動作ログを入れて貰います。
- peerCount が常に 0 になっています。送受信のところにログを入れて、動作確認できるようにして。

ログをチャットに貼り付けて、分析してもらいます。

原因として ScanFilter.setServiceUuid() がうまく働いていない可能性があるとのことなので、ScanFilter を AI が外してみます。

ログは出るようになったのですが、まだ受信できません。
いったん保留
そもそも、手元の Android 端末が BLE5 Extended Advertising を通しているか不明なので、ちょっと方式変えて、疎通試験用のライブラリを作っていくことにします。
