Claude Code for Android Development (2026)
To use Claude Code for Android development, open your project in Android Studio, start Claude Code in the project root, and prompt it with your target feature plus the relevant files in context. Claude Code reads your existing architecture — Gradle config, package structure, existing ViewModels — and generates idiomatic Kotlin that fits the codebase. It handles Jetpack Compose UI, ViewModel + Coroutines state management, Room database schemas, Retrofit API clients, and Hilt dependency injection without switching tools or copy-pasting boilerplate. This guide covers the exact workflow for each layer of a production Android app.
Setting Up a New Android Project with Claude Code
After creating a project in Android Studio (Empty Activity with Compose), open Claude Code in the project root and establish the architecture:
I have a new Android project using:
- Kotlin 2.0, minSdk 26, targetSdk 35
- Jetpack Compose with Material3
- Single-activity architecture
Set up the following:
- app/build.gradle.kts with dependencies for Compose, ViewModel,
Room 2.6, Retrofit 2.11, Hilt 2.51, Coroutines
- buildSrc/Dependencies.kt for version constants
- com.example.app package with ui/, data/, domain/ layers
- Hilt application class and manifest entry
Show all files with correct versions for 2026.
Claude Code reads your existing build.gradle.kts and generates a consistent dependency block — avoiding the version conflicts that typically consume hours of setup time.
For adding Claude Code to an existing project:
Review my app/build.gradle.kts and identify missing dependencies
for: Jetpack Compose (if not present), Hilt, Room, and Retrofit.
Show only the additions needed, preserving existing versions.
Generating Jetpack Compose UIs
Compose screen generation is where Claude Code delivers the highest time savings. Provide the data model and describe the layout:
Generate a Jetpack Compose screen for a notes list with:
- NoteListScreen composable that takes NoteListUiState and callbacks
- LazyColumn with NoteCard items (title, preview, timestamp)
- FAB to create a new note
- Empty state illustration when list is empty
- Material3 theming (use Surface, TopAppBar, FloatingActionButton)
- Preview annotations for light and dark themes
Claude Code produces working Compose code with correct state hoisting and preview support:
// ui/notes/NoteListScreen.kt
@Composable
fun NoteListScreen(
uiState: NoteListUiState,
onNoteClick: (Long) -> Unit,
onCreateNote: () -> Unit,
modifier: Modifier = Modifier
) {
Scaffold(
topBar = {
TopAppBar(title = { Text("Notes") })
},
floatingActionButton = {
FloatingActionButton(onClick = onCreateNote) {
Icon(Icons.Default.Add, contentDescription = "New note")
}
},
modifier = modifier
) { paddingValues ->
when {
uiState.isLoading -> CircularProgressIndicator(
modifier = Modifier.fillMaxSize().wrapContentSize()
)
uiState.notes.isEmpty() -> EmptyNotesState(
modifier = Modifier.fillMaxSize().padding(paddingValues)
)
else -> LazyColumn(contentPadding = paddingValues) {
items(uiState.notes, key = { it.id }) { note ->
NoteCard(note = note, onClick = { onNoteClick(note.id) })
}
}
}
}
}
@Composable
private fun NoteCard(note: NoteUi, onClick: () -> Unit) {
Card(
onClick = onClick,
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 4.dp)
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(note.title, style = MaterialTheme.typography.titleMedium)
Spacer(Modifier.height(4.dp))
Text(
note.preview,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 2,
overflow = TextOverflow.Ellipsis
)
Text(
note.formattedDate,
style = MaterialTheme.typography.labelSmall,
modifier = Modifier.align(Alignment.End).padding(top = 8.dp)
)
}
}
}
@Preview(showBackground = true)
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun NoteListScreenPreview() {
AppTheme {
NoteListScreen(
uiState = NoteListUiState(notes = SampleData.notes),
onNoteClick = {},
onCreateNote = {}
)
}
}
Power Prompts ($29) includes 50+ prompts for Compose screen generation, navigation graph setup, custom theming, animation patterns, and accessibility — ready to use in Claude Code.
ViewModel + Coroutines Patterns
Ask Claude Code to generate the full ViewModel with state management:
Generate NoteListViewModel for the notes screen with:
- StateFlow<NoteListUiState> exposed to the UI
- loadNotes() using viewModelScope + Dispatchers.IO
- deleteNote(id: Long) with optimistic UI update and error rollback
- Hilt @HiltViewModel injection of NoteRepository
- sealed class NoteListUiState with Loading, Success, Error states
// ui/notes/NoteListViewModel.kt
@HiltViewModel
class NoteListViewModel @Inject constructor(
private val noteRepository: NoteRepository
) : ViewModel() {
private val _uiState = MutableStateFlow<NoteListUiState>(NoteListUiState.Loading)
val uiState: StateFlow<NoteListUiState> = _uiState.asStateFlow()
init {
observeNotes()
}
private fun observeNotes() {
viewModelScope.launch {
noteRepository.getAllNotes()
.map { notes -> NoteListUiState.Success(notes.map { it.toUi() }) }
.catch { e -> emit(NoteListUiState.Error(e.message ?: "Unknown error")) }
.collect { _uiState.value = it }
}
}
fun deleteNote(id: Long) {
val previousState = _uiState.value
// Optimistic update
if (previousState is NoteListUiState.Success) {
_uiState.value = previousState.copy(
notes = previousState.notes.filter { it.id != id }
)
}
viewModelScope.launch {
runCatching { noteRepository.deleteNote(id) }
.onFailure { _uiState.value = previousState } // rollback
}
}
}
sealed class NoteListUiState {
data object Loading : NoteListUiState()
data class Success(val notes: List<NoteUi>) : NoteListUiState()
data class Error(val message: String) : NoteListUiState()
}
Room Database Integration
Generate a Room database setup for notes with:
- NoteEntity data class with id, title, content, createdAt, updatedAt, isSynced
- NoteDao with queries: getAll() as Flow, getById(), insert(), update(), delete(),
getUnsyncedNotes() for offline sync
- AppDatabase with migration strategy
- TypeConverter for LocalDateTime
// data/local/NoteEntity.kt
@Entity(tableName = "notes")
data class NoteEntity(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val title: String,
val content: String,
val createdAt: LocalDateTime = LocalDateTime.now(),
val updatedAt: LocalDateTime = LocalDateTime.now(),
val isSynced: Boolean = false
)
// data/local/NoteDao.kt
@Dao
interface NoteDao {
@Query("SELECT * FROM notes ORDER BY updatedAt DESC")
fun getAll(): Flow<List<NoteEntity>>
@Query("SELECT * FROM notes WHERE id = :id")
suspend fun getById(id: Long): NoteEntity?
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(note: NoteEntity): Long
@Update
suspend fun update(note: NoteEntity)
@Query("DELETE FROM notes WHERE id = :id")
suspend fun delete(id: Long)
@Query("SELECT * FROM notes WHERE isSynced = 0")
suspend fun getUnsyncedNotes(): List<NoteEntity>
}
Retrofit API Client Generation
Generate a Retrofit API client for a notes sync backend:
- NoteApiService interface with: GET /notes, POST /notes, PUT /notes/{id},
DELETE /notes/{id}
- NoteApiModule Hilt module providing OkHttpClient and Retrofit
- Add an auth interceptor that reads a token from DataStore
- Response/request DTOs as data classes with kotlinx.serialization
Claude Code generates a complete network layer including the Hilt module wiring, authentication interceptor, and JSON serialization setup — typically 80+ lines that would otherwise require careful manual assembly.
Hilt Dependency Injection
Generate Hilt modules for the notes app:
- DatabaseModule: provides AppDatabase and NoteDao (singleton, application scope)
- NetworkModule: provides OkHttpClient, Retrofit, NoteApiService (singleton)
- RepositoryModule: binds NoteRepositoryImpl to NoteRepository interface
- DataStoreModule: provides DataStore<Preferences> for auth token
Use @InstallIn(SingletonComponent::class) for all modules.
Claude Code correctly separates concerns across modules and uses @Binds vs @Provides appropriately — a distinction that causes subtle Hilt errors when done manually.
Instrumented Test Generation
See the Claude Code test generation guide for the full testing workflow. For Android specifically:
Generate instrumented tests for NoteListScreen using Compose testing:
- Test that NoteCard renders title and preview correctly
- Test FAB triggers onCreateNote callback
- Test empty state is shown when notes list is empty
- Test loading state shows CircularProgressIndicator
Use ComposeTestRule and SemanticsNodeInteraction assertions.
For ViewModel unit tests:
Generate unit tests for NoteListViewModel using:
- kotlinx.coroutines.test (runTest, TestDispatcher)
- MockK for NoteRepository mock
- Test: initial load populates Success state
- Test: deleteNote performs optimistic update and rolls back on error
- Test: error in repository emits Error state
Workflow Example: Build a Notes App with Offline Sync
This sequence of prompts produces a complete notes app with local-first architecture:
Step 1 — Data layer:
Create the full Room + Repository layer for a notes app.
NoteRepository interface with: getAllNotes(): Flow<List<Note>>,
getNoteById(id): Note?, saveNote(note): Long, deleteNote(id),
syncNotes(): Result<Unit>. Implement NoteRepositoryImpl injecting
NoteDao and NoteApiService. syncNotes() should: fetch remote notes,
upsert locally, then push unsynced local notes to the API and mark
them synced. Use Result<T> for error handling.
Step 2 — Domain models and mappers:
Create domain models (Note, NoteUi) and mapper extensions:
NoteEntity.toDomain(), Note.toEntity(), Note.toApiDto(),
NoteApiDto.toDomain(). Place in domain/model/ and data/mapper/.
Step 3 — Navigation:
Set up Compose Navigation for the notes app:
- NavGraph with NoteList (home), NoteDetail (noteId: Long), NoteEdit
(optional noteId for create/edit) destinations
- Type-safe navigation using @Serializable route objects (Navigation 2.8+)
- Transitions: slide in/out horizontally between list and detail
Step 4 — Background sync:
Create a WorkManager worker SyncNotesWorker that:
- Injects NoteRepository via HiltWorker
- Calls syncNotes() and retries up to 3 times on network failure
- Schedules as periodic work every 15 minutes with network constraint
- Show a notification when sync completes with new notes count
Register in AppModule with a one-time initial sync on app start.
Step 5 — Polish and edge cases:
Review the complete notes app implementation and add:
- Conflict resolution when same note is edited offline and online
(last-write-wins using updatedAt timestamp)
- Soft delete: mark notes as deleted locally, sync deletions, then purge
- Pull-to-refresh gesture on NoteListScreen that triggers syncNotes()
Show only the changed files.
Each prompt builds on the previous. Claude Code maintains context across the session, so later prompts reference earlier decisions correctly. See the Claude Code complete guide for context window management strategies.
How Claude Code Compares to Copilot for Android
For Android specifically, Claude Code's project-wide context awareness outperforms inline completions. When generating a Hilt module, Claude Code reads your existing module files, detects the scope conventions you're using, and matches them — rather than generating generic boilerplate that requires manual adjustment. The Claude Code React Native mobile guide covers a similar comparison for cross-platform projects.
Power Prompts ($29) includes prompts for Android architecture setup, Compose navigation, Room migrations, WorkManager patterns, Play Store submission, and ProGuard/R8 configuration — 300 prompts total.
Frequently Asked Questions
Does Claude Code work inside Android Studio?
Claude Code runs in the terminal, not as an Android Studio plugin. Open a terminal tab within Android Studio (View → Tool Windows → Terminal) and run claude in the project root. Claude Code reads the same files Android Studio has open, so there is no conflict. Some developers keep Android Studio and Claude Code in a split-screen layout — Android Studio for the emulator and visual layout editor, Claude Code for code generation.
Can Claude Code generate Compose Previews and catch UI bugs?
Claude Code generates @Preview annotations correctly, including multi-preview setups for different screen sizes and themes. It cannot render them visually — that still requires Android Studio's Preview pane. However, Claude Code can review a screenshot of your Preview output if you describe visual issues: paste the screenshot path and describe what looks wrong, and Claude Code suggests Compose modifier corrections.
How does Claude Code handle Gradle version conflicts?
Give Claude Code your full build.gradle.kts and describe the error. It identifies incompatible version combinations (common with Compose BOM vs. manual artifact versions, or Hilt vs. KSP version mismatches) and provides the correct pinned versions. For KSP-based libraries (Room, Hilt), always ensure the KSP version matches your Kotlin version — Claude Code checks this automatically when you include your libs.versions.toml.
Can Claude Code write Espresso or UI Automator tests?
Yes. Provide the screen under test and the user flow to verify:
Generate an Espresso test that: launches MainActivity, navigates to
NoteListScreen, taps the FAB, enters a title and content in NoteEditScreen,
presses Save, and verifies the new note appears in the list.
Claude Code generates the test with proper ActivityScenario, Intents, and EspressoIdlingResource setup for async operations. For Compose-heavy UIs, the Compose testing API (composeTestRule) is preferable and Claude Code defaults to it when it detects Compose screens.
Does Claude Code understand the Android lifecycle?
Claude Code knows that ViewModels survive configuration changes, that LaunchedEffect keys control recomposition side effects, and that collectAsStateWithLifecycle is preferred over collectAsState for battery efficiency. When generating ViewModel code it consistently uses viewModelScope, avoids leaking coroutines, and places StateFlow over LiveData for new Compose projects. If your project still uses LiveData, specify that in the prompt and Claude Code adapts accordingly.