android-development

Android development with Kotlin, Jetpack Compose, and modern Android architecture. Use when building Android apps, implementing Material Design, or following Android best practices.

31 stars

Best use case

android-development is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Android development with Kotlin, Jetpack Compose, and modern Android architecture. Use when building Android apps, implementing Material Design, or following Android best practices.

Teams using android-development should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/android-development/SKILL.md --create-dirs "https://raw.githubusercontent.com/travisjneuman/.claude/main/skills/android-development/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/android-development/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How android-development Compares

Feature / Agentandroid-developmentStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Android development with Kotlin, Jetpack Compose, and modern Android architecture. Use when building Android apps, implementing Material Design, or following Android best practices.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Android Development

Comprehensive guide for building modern Android applications.

## Platforms Covered

| Platform       | Min SDK      | Target SDK  |
| -------------- | ------------ | ----------- |
| Android Phone  | API 24 (7.0) | API 34 (14) |
| Android Tablet | API 24       | API 34      |
| Android TV     | API 24       | API 34      |
| Wear OS        | API 30       | API 34      |
| Android Auto   | API 29       | API 34      |

---

## Jetpack Compose (Modern UI)

### Basic Structure

```kotlin
@Composable
fun MyApp() {
    MaterialTheme {
        Surface(
            modifier = Modifier.fillMaxSize(),
            color = MaterialTheme.colorScheme.background
        ) {
            MainScreen()
        }
    }
}

@Composable
fun MainScreen() {
    var count by remember { mutableStateOf(0) }

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Text(
            text = "Count: $count",
            style = MaterialTheme.typography.headlineLarge
        )

        Spacer(modifier = Modifier.height(16.dp))

        Button(onClick = { count++ }) {
            Text("Increment")
        }
    }
}
```

### State Management

```kotlin
// Local state
var text by remember { mutableStateOf("") }

// State hoisting
@Composable
fun StatefulCounter() {
    var count by remember { mutableStateOf(0) }
    StatelessCounter(count = count, onIncrement = { count++ })
}

@Composable
fun StatelessCounter(count: Int, onIncrement: () -> Unit) {
    Button(onClick = onIncrement) {
        Text("Count: $count")
    }
}

// ViewModel state
@HiltViewModel
class MainViewModel @Inject constructor(
    private val repository: ItemRepository
) : ViewModel() {

    private val _uiState = MutableStateFlow(UiState())
    val uiState: StateFlow<UiState> = _uiState.asStateFlow()

    fun loadItems() {
        viewModelScope.launch {
            _uiState.update { it.copy(isLoading = true) }
            try {
                val items = repository.getItems()
                _uiState.update { it.copy(items = items, isLoading = false) }
            } catch (e: Exception) {
                _uiState.update { it.copy(error = e.message, isLoading = false) }
            }
        }
    }
}

// Collecting in Compose
@Composable
fun MainScreen(viewModel: MainViewModel = hiltViewModel()) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()

    when {
        uiState.isLoading -> LoadingIndicator()
        uiState.error != null -> ErrorMessage(uiState.error!!)
        else -> ItemList(uiState.items)
    }
}
```

### Navigation

```kotlin
// Navigation setup
@Composable
fun AppNavigation() {
    val navController = rememberNavController()

    NavHost(navController = navController, startDestination = "home") {
        composable("home") {
            HomeScreen(
                onNavigateToDetail = { id ->
                    navController.navigate("detail/$id")
                }
            )
        }
        composable(
            route = "detail/{itemId}",
            arguments = listOf(navArgument("itemId") { type = NavType.StringType })
        ) { backStackEntry ->
            val itemId = backStackEntry.arguments?.getString("itemId")
            DetailScreen(itemId = itemId)
        }
    }
}

// Type-safe navigation (recommended)
@Serializable
data class DetailRoute(val itemId: String)

navController.navigate(DetailRoute(itemId = "123"))
```

### Lists

```kotlin
@Composable
fun ItemList(items: List<Item>) {
    LazyColumn(
        modifier = Modifier.fillMaxSize(),
        contentPadding = PaddingValues(16.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        items(
            items = items,
            key = { it.id }
        ) { item ->
            ItemCard(item = item)
        }
    }
}

// Pull to refresh
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun RefreshableList(
    items: List<Item>,
    isRefreshing: Boolean,
    onRefresh: () -> Unit
) {
    val pullRefreshState = rememberPullToRefreshState()

    PullToRefreshBox(
        isRefreshing = isRefreshing,
        onRefresh = onRefresh,
        state = pullRefreshState
    ) {
        LazyColumn { /* content */ }
    }
}
```

---

## Modern Android Architecture

### Clean Architecture Layers

```
app/
├── data/
│   ├── local/
│   │   ├── AppDatabase.kt
│   │   └── ItemDao.kt
│   ├── remote/
│   │   ├── ApiService.kt
│   │   └── ItemDto.kt
│   └── repository/
│       └── ItemRepositoryImpl.kt
├── domain/
│   ├── model/
│   │   └── Item.kt
│   ├── repository/
│   │   └── ItemRepository.kt
│   └── usecase/
│       └── GetItemsUseCase.kt
├── presentation/
│   ├── home/
│   │   ├── HomeScreen.kt
│   │   └── HomeViewModel.kt
│   └── navigation/
│       └── AppNavigation.kt
└── di/
    └── AppModule.kt
```

### Dependency Injection (Hilt)

```kotlin
@HiltAndroidApp
class MyApplication : Application()

@Module
@InstallIn(SingletonComponent::class)
object AppModule {

    @Provides
    @Singleton
    fun provideDatabase(@ApplicationContext context: Context): AppDatabase {
        return Room.databaseBuilder(
            context,
            AppDatabase::class.java,
            "app_database"
        ).build()
    }

    @Provides
    @Singleton
    fun provideApiService(): ApiService {
        return Retrofit.Builder()
            .baseUrl("https://api.example.com/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(ApiService::class.java)
    }

    @Provides
    @Singleton
    fun provideItemRepository(
        apiService: ApiService,
        database: AppDatabase
    ): ItemRepository {
        return ItemRepositoryImpl(apiService, database.itemDao())
    }
}
```

---

## Data Layer

### Room Database

```kotlin
@Entity(tableName = "items")
data class ItemEntity(
    @PrimaryKey val id: String,
    val name: String,
    val createdAt: Long
)

@Dao
interface ItemDao {
    @Query("SELECT * FROM items ORDER BY createdAt DESC")
    fun getItems(): Flow<List<ItemEntity>>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertItems(items: List<ItemEntity>)

    @Delete
    suspend fun deleteItem(item: ItemEntity)
}

@Database(entities = [ItemEntity::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun itemDao(): ItemDao
}
```

### Retrofit API

```kotlin
interface ApiService {
    @GET("items")
    suspend fun getItems(): List<ItemDto>

    @POST("items")
    suspend fun createItem(@Body item: CreateItemRequest): ItemDto

    @DELETE("items/{id}")
    suspend fun deleteItem(@Path("id") id: String)
}

data class ItemDto(
    val id: String,
    val name: String,
    @SerializedName("created_at") val createdAt: String
)
```

### Repository Pattern

```kotlin
interface ItemRepository {
    fun getItems(): Flow<List<Item>>
    suspend fun refreshItems()
    suspend fun deleteItem(id: String)
}

class ItemRepositoryImpl @Inject constructor(
    private val apiService: ApiService,
    private val itemDao: ItemDao
) : ItemRepository {

    override fun getItems(): Flow<List<Item>> {
        return itemDao.getItems().map { entities ->
            entities.map { it.toDomain() }
        }
    }

    override suspend fun refreshItems() {
        val items = apiService.getItems()
        itemDao.insertItems(items.map { it.toEntity() })
    }
}
```

---

## Material Design 3

### Theme Setup

```kotlin
@Composable
fun MyAppTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    dynamicColor: Boolean = true,
    content: @Composable () -> Unit
) {
    val colorScheme = when {
        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
            val context = LocalContext.current
            if (darkTheme) dynamicDarkColorScheme(context)
            else dynamicLightColorScheme(context)
        }
        darkTheme -> DarkColorScheme
        else -> LightColorScheme
    }

    MaterialTheme(
        colorScheme = colorScheme,
        typography = Typography,
        content = content
    )
}
```

### Common Components

```kotlin
// Top App Bar
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MyTopBar(
    title: String,
    onBackClick: () -> Unit
) {
    TopAppBar(
        title = { Text(title) },
        navigationIcon = {
            IconButton(onClick = onBackClick) {
                Icon(Icons.Default.ArrowBack, contentDescription = "Back")
            }
        },
        actions = {
            IconButton(onClick = { /* menu */ }) {
                Icon(Icons.Default.MoreVert, contentDescription = "Menu")
            }
        }
    )
}

// Bottom Navigation
@Composable
fun MyBottomBar(
    selectedTab: Int,
    onTabSelected: (Int) -> Unit
) {
    NavigationBar {
        NavigationBarItem(
            icon = { Icon(Icons.Default.Home, contentDescription = null) },
            label = { Text("Home") },
            selected = selectedTab == 0,
            onClick = { onTabSelected(0) }
        )
        NavigationBarItem(
            icon = { Icon(Icons.Default.Settings, contentDescription = null) },
            label = { Text("Settings") },
            selected = selectedTab == 1,
            onClick = { onTabSelected(1) }
        )
    }
}
```

---

## Platform-Specific

### Android TV

```kotlin
// Focus management
@Composable
fun TvButton(
    onClick: () -> Unit,
    content: @Composable () -> Unit
) {
    var isFocused by remember { mutableStateOf(false) }

    Box(
        modifier = Modifier
            .onFocusChanged { isFocused = it.isFocused }
            .focusable()
            .clickable(onClick = onClick)
            .background(
                if (isFocused) MaterialTheme.colorScheme.primary
                else MaterialTheme.colorScheme.surface
            )
    ) {
        content()
    }
}
```

### Wear OS

```kotlin
@Composable
fun WearApp() {
    MaterialTheme {
        ScalingLazyColumn(
            modifier = Modifier.fillMaxSize(),
            anchorType = ScalingLazyListAnchorType.ItemCenter
        ) {
            item { TimeText() }
            item {
                Chip(
                    onClick = { },
                    label = { Text("Action") }
                )
            }
        }
    }
}
```

---

## Testing

### Unit Tests

```kotlin
@Test
fun `getItems returns mapped domain objects`() = runTest {
    val repository = ItemRepositoryImpl(
        apiService = FakeApiService(),
        itemDao = FakeItemDao()
    )

    val items = repository.getItems().first()

    assertEquals(2, items.size)
    assertEquals("Item 1", items[0].name)
}
```

### Compose UI Tests

```kotlin
@HiltAndroidTest
class MainScreenTest {
    @get:Rule
    val composeTestRule = createAndroidComposeRule<MainActivity>()

    @Test
    fun displayItems_whenLoaded() {
        composeTestRule.onNodeWithText("Item 1").assertIsDisplayed()
        composeTestRule.onNodeWithText("Item 2").assertIsDisplayed()
    }

    @Test
    fun navigateToDetail_onItemClick() {
        composeTestRule.onNodeWithText("Item 1").performClick()
        composeTestRule.onNodeWithText("Item Details").assertIsDisplayed()
    }
}
```

---

## Play Store Requirements

### Required

- Privacy policy
- App icon (512x512)
- Feature graphic (1024x500)
- Screenshots (min 2)
- Target API 34+

### App Bundle

```groovy
android {
    bundle {
        language {
            enableSplit = true
        }
        density {
            enableSplit = true
        }
        abi {
            enableSplit = true
        }
    }
}
```

---

## Best Practices

### DO:

- Use Kotlin Coroutines and Flow
- Follow unidirectional data flow
- Use Jetpack Compose for new UI
- Implement proper lifecycle handling
- Support multiple screen sizes

### DON'T:

- Block main thread
- Leak contexts
- Hardcode dimensions
- Ignore process death
- Skip ProGuard rules

---

## Kotlin Multiplatform (KMP) / Compose Multiplatform (CMP)

### Shared Business Logic with KMP

```kotlin
// shared/src/commonMain/kotlin/UserRepository.kt
expect class PlatformContext

class UserRepository(private val api: ApiService, private val db: Database) {
    suspend fun getUsers(): List<User> {
        return try {
            val remote = api.fetchUsers()
            db.saveUsers(remote)
            remote
        } catch (e: Exception) {
            db.getCachedUsers()
        }
    }
}

// shared/src/androidMain/kotlin/PlatformContext.kt
actual class PlatformContext(val context: Context)

// shared/src/iosMain/kotlin/PlatformContext.kt
actual class PlatformContext
```

### Compose Multiplatform (Shared UI)

```kotlin
// shared/src/commonMain/kotlin/App.kt
@Composable
fun App() {
    MaterialTheme {
        var users by remember { mutableStateOf<List<User>>(emptyList()) }
        val repository = remember { UserRepository() }

        LaunchedEffect(Unit) {
            users = repository.getUsers()
        }

        LazyColumn {
            items(users) { user ->
                UserCard(user)
            }
        }
    }
}

// Runs natively on Android, iOS, Desktop, and Web
```

### KMP Project Structure

```
project/
├── shared/
│   └── src/
│       ├── commonMain/     # Shared Kotlin code
│       ├── androidMain/    # Android-specific
│       ├── iosMain/        # iOS-specific
│       └── desktopMain/    # Desktop-specific
├── androidApp/             # Android entry point
├── iosApp/                 # iOS entry point (Swift/SwiftUI)
└── desktopApp/             # Desktop entry point
```

---

## Navigation 3 (Type-Safe Navigation)

```kotlin
// Define routes as serializable data classes
@Serializable
data object Home

@Serializable
data class Detail(val itemId: String)

@Serializable
data class Settings(val section: String? = null)

// Navigation setup
@Composable
fun AppNavHost(navController: NavHostController = rememberNavController()) {
    NavHost(navController = navController, startDestination = Home) {
        composable<Home> {
            HomeScreen(onItemClick = { id ->
                navController.navigate(Detail(itemId = id))
            })
        }
        composable<Detail> { backStackEntry ->
            val detail: Detail = backStackEntry.toRoute()
            DetailScreen(itemId = detail.itemId)
        }
        composable<Settings> { backStackEntry ->
            val settings: Settings = backStackEntry.toRoute()
            SettingsScreen(section = settings.section)
        }
    }
}
```

---

## API 35 (Android 15) Features

| Feature                    | Description                                           |
| -------------------------- | ----------------------------------------------------- |
| **Edge-to-edge enforced**  | Apps must handle insets properly                       |
| **Predictive back**        | System back gesture with preview animations           |
| **Private Space**          | User-controlled hidden app profile                    |
| **Satellite connectivity** | SMS/MMS over satellite                                |
| **App archival**           | Auto-archive unused apps, preserve data               |
| **Health Connect**         | Updated health data APIs                              |

```kotlin
// Edge-to-edge (required on API 35+)
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    enableEdgeToEdge()

    setContent {
        Scaffold(
            modifier = Modifier.fillMaxSize(),
            contentWindowInsets = ScaffoldDefaults.contentWindowInsets,
        ) { innerPadding ->
            MainContent(modifier = Modifier.padding(innerPadding))
        }
    }
}

// Predictive back with Compose
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DetailScreen(onBack: () -> Unit) {
    BackHandler(onBack = onBack)
    // Content renders with predictive back animation automatically
}
```

---

## Baseline Profiles for Startup Performance

```kotlin
// baselineprofile/build.gradle.kts
dependencies {
    implementation("androidx.benchmark:benchmark-macro-junit4:1.3.0")
}

// BaselineProfileGenerator.kt
@RunWith(AndroidJUnit4::class)
class BaselineProfileGenerator {
    @get:Rule
    val rule = BaselineProfileRule()

    @Test
    fun generateBaselineProfile() {
        rule.collect(packageName = "com.example.app") {
            // Critical user journey
            pressHome()
            startActivityAndWait()
            device.findObject(By.text("Login")).click()
            device.wait(Until.hasObject(By.text("Dashboard")), 5000)
        }
    }
}
```

```groovy
// app/build.gradle.kts
android {
    buildTypes {
        release {
            // Baseline profiles improve startup by 15-30%
            baselineProfile.automaticGenerationDuringBuild = true
        }
    }
}
```

---

## Credential Manager API

```kotlin
// Modern sign-in replacing legacy APIs
val credentialManager = CredentialManager.create(context)

// Sign in with passkeys, passwords, or federated identity
suspend fun signIn() {
    val request = GetCredentialRequest(listOf(
        GetPasswordOption(),
        GetPublicKeyCredentialOption(requestJson = passkeyRequestJson),
        GetGoogleIdOption(serverClientId = WEB_CLIENT_ID),
    ))

    try {
        val result = credentialManager.getCredential(context, request)
        handleSignIn(result)
    } catch (e: GetCredentialException) {
        handleSignInError(e)
    }
}

// Create a passkey
suspend fun createPasskey() {
    val request = CreatePublicKeyCredentialRequest(
        requestJson = createPasskeyRequestJson
    )
    val result = credentialManager.createCredential(context, request)
    handlePasskeyCreation(result)
}
```

Related Skills

svelte-development

31
from travisjneuman/.claude

Svelte 5 development with runes ($state, $derived, $effect), SvelteKit full-stack framework, and modern reactive patterns. Use when building Svelte applications, implementing fine-grained reactivity, or working with SvelteKit routing and server functions.

serverless-development

31
from travisjneuman/.claude

AWS Lambda, Vercel Edge Functions, Cloudflare Workers, cold starts, deployment patterns, and infrastructure as code (SST, Serverless Framework). Use when building serverless applications or optimizing function-based architectures.

pwa-development

31
from travisjneuman/.claude

Progressive Web App development for installable, offline-capable web applications. Use when building PWAs, implementing service workers, or creating offline-first experiences.

llm-app-development

31
from travisjneuman/.claude

LLM app development with RAG, prompt engineering, vector databases, and AI agents

ios-development

31
from travisjneuman/.claude

iOS, iPadOS, and tvOS development with Swift, SwiftUI, and UIKit. Use when building Apple platform apps, implementing iOS-specific features, or following Apple Human Interface Guidelines.

game-development

31
from travisjneuman/.claude

Game development with Unity, Unreal Engine, and Godot. Use when building games, implementing game mechanics, physics, AI, or working with game engines.

flutter-development

31
from travisjneuman/.claude

Cross-platform development with Flutter and Dart for iOS, Android, Web, Desktop, and embedded. Use when building Flutter apps, implementing Material/Cupertino design, or optimizing Dart code.

example-skill

31
from travisjneuman/.claude

Example skill - replace with your skill's description and activation keywords

websockets-realtime

31
from travisjneuman/.claude

Real-time communication with WebSockets, Server-Sent Events, and related technologies. Use when building chat, live updates, collaborative features, or any real-time functionality.

video-production

31
from travisjneuman/.claude

Professional video production from planning to delivery. Use when creating video content, editing workflows, motion graphics, or optimizing video for different platforms.

ui-research

31
from travisjneuman/.claude

Research-first UI/UX design workflow. Use BEFORE any frontend visual work to research modern patterns, gather inspiration from real products, and avoid generic AI-generated looks. Mandatory prerequisite for quality UI work.

ui-animation

31
from travisjneuman/.claude

Motion design and animation for user interfaces. Use when creating micro-interactions, page transitions, loading states, or any UI animation across web and mobile platforms.