Claude CodeにJetpack Composeを書かせる:CLAUDE.mdでXML・AsyncTask・LiveDataを封じる実践ガイド

はじめに

AndroidはXML→Compose、AsyncTask→Coroutines、Dagger→HiltとパラダイムシフトしてきたためClaude Codeが旧パターンを生成しやすい。本記事はKotlin/Android Nativeに特化し、CLAUDE.mdで旧来パターンを封じる方法を整理する。


Claude Codeが生成しやすい古いAndroidパターンを正す

ViewBinding(XMLレイアウト参照)→ Jetpack Compose


// ❌ ViewBinding
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.button.setOnClickListener { binding.textView.text = "Clicked!" }

// ✅ Jetpack Compose
@Composable
fun MainScreen(text: String, onButtonClick: () -> Unit) {
    Column(Modifier.fillMaxSize().padding(16.dp)) {
        Text(text = text)
        Button(onClick = onButtonClick) { Text("Click me") }
    }
}

AsyncTask → viewModelScope + Coroutines

AsyncTaskはAPI Level 30(Android 11)でdeprecated。


// ❌ AsyncTask(API Level 30でdeprecated)
class FetchTask : AsyncTask<Void, Void, String>() {
    override fun doInBackground(vararg p: Void) = api.fetchData()
    override fun onPostExecute(result: String) { textView.text = result }
}
FetchTask().execute()

// ✅ viewModelScope.launch(核心部分)
fun fetchData() {
    viewModelScope.launch {
        _uiState.value = try { UiState.Success(repo.fetchData()) }
                         catch (e: Exception) { UiState.Error(e.message ?: "Error") }
    }
}

viewModelScopeはViewModelが破棄される際に自動キャンセルする。I/O処理はRepository層のsuspend関数内でwithContext(Dispatchers.IO)を使う。

ViewModelFactory手動実装 + LiveData → @HiltViewModel + StateFlow


// ❌ ViewModelFactory手動実装 + LiveData
class Factory(val r: DataRepository) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(c: Class<T>): T = OldViewModel(r) as T
}
class OldViewModel(val r: DataRepository) : ViewModel() {
    val data: LiveData<String> = MutableLiveData()
}

// ✅ @HiltViewModel + StateFlow
@HiltViewModel
class MainViewModel @Inject constructor(val repo: DataRepository) : ViewModel() {
    private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
    val uiState: StateFlow<UiState> = _uiState
}
// Composable: val s by vm.uiState.collectAsStateWithLifecycle()
// 依存: lifecycle-runtime-compose:2.10.0

CLAUDE.mdで制御する完全テンプレート


# Android/Kotlin プロジェクト設定

技術スタック: Kotlin / Jetpack Compose / Coroutines+Flow / Hilt 2.x / MVVM+Repository

## UI(Jetpack Compose)
- UIはすべて @Composable 関数で実装(XMLレイアウト禁止・setContentView()禁止)
- State HoistingパターンでステートレスComposableを優先する
- リスト: LazyColumn / LazyRow(RecyclerView禁止)

## 状態管理
- ViewModel: MutableStateFlow(private)→ StateFlow(public)で公開(LiveData禁止)
- UiState: sealed class(Loading・Success・Error)
- Composable: collectAsStateWithLifecycle() で収集(lifecycle-runtime-compose:2.10.0)

## 非同期処理
- AsyncTask・Thread・Handler 禁止(AsyncTask はAPI Level 30 deprecated)
- ViewModel: viewModelScope.launch / I/O: withContext(Dispatchers.IO)

## 依存注入(Hilt)
- @HiltAndroidApp / @AndroidEntryPoint / @HiltViewModel を使う
- @Inject constructor で宣言(ViewModelFactory手動実装禁止)

## 禁止事項
- nullable強制解除(!!)禁止 → ?.let {} またはElvis演算子(?:)
- XMLレイアウト・LiveData・ViewModelFactory手動実装は新規コードで使用禁止

PostToolUseフックでktlintを自動実行する

.ktファイル編集後にktlintで自動フォーマットする(事前にbrew install ktlintが必要)。


{
  "hooks": {
    "PostToolUse": [{
      "matcher": "Write|Edit",
      "hooks": [{
        "type": "command",
        "command": "FILE=$(jq -r '.tool_input.file_path // empty'); [[ \"$FILE\" == *.kt ]] && ktlint --format \"$FILE\" 2>/dev/null; true"
      }]
    }]
  }
}

EM視点──AndroidチームへのClaude Code展開

新規プロジェクトはCLAUDE.mdを最初から設置してCompose専用として始める。XMLが混在する既存プロジェクトでは「新規画面はCompose、既存XMLはマイグレーション対象として触らない」と明記して段階移行を管理する。CLAUDE.mdと.claude/settings.jsonをリポジトリにコミットすれば、JuniorエンジニアがClaude Codeを使っても旧パターンが混入しない。


まとめ

Claude CodeがAndroidで生成しやすい旧パターンはViewBinding・AsyncTask・ViewModelFactory+LiveDataだ。本記事のCLAUDE.mdテンプレートを今すぐプロジェクトにコミットしてほしい。ktlintのPostToolUseフックを合わせて設定すれば、スタイル違反もその場で自動修正される。

コメント

タイトルとURLをコピーしました