Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
alinaqi avatar

Android Kotlin

  • 2.9k installs
  • 706 repo stars
  • Updated July 14, 2026
  • alinaqi/claude-bootstrap

android-kotlin is an agent skill that guides Kotlin Android apps with Jetpack Compose, Coroutines, Hilt, Room, and MockK testing patterns.

About

android-kotlin is an agent skill for Android development using Kotlin coroutines, Jetpack Compose, Hilt dependency injection, Room persistence, and MockK testing conventions. It documents a layered project layout with data, domain, and ui packages, Gradle Kotlin DSL setup with Compose BOM, Hilt KSP processors, Room, and coroutines-test plus Turbine for Flow testing. ViewModel examples use HiltViewModel, MutableStateFlow uiState, viewModelScope launches, and SavedStateHandle arguments. Repository patterns emit cached Room data first then refresh from Retrofit or Ktor remote services on Dispatchers.IO. Testing guidance covers MockK, kotlinx-coroutines-test, and Compose ui-test-junit4 instrumentation. The skill auto-applies on Kotlin and Gradle paths listed in metadata when working inside Android modules. Developers reach for it when scaffolding Compose screens, wiring Hilt modules, implementing Flow-based repositories, or aligning Gradle dependencies with current Android toolchain versions across app modules.

  • Layered package layout separates data, domain, di, and ui feature modules.
  • Gradle template pins Compose BOM, Hilt KSP, Room, and coroutines dependencies.
  • ViewModel pattern uses StateFlow uiState with viewModelScope and SavedStateHandle args.
  • Repository Flow emits local Room cache before network refresh on IO dispatcher.
  • Testing stack includes MockK, coroutines-test, Turbine, and Compose ui-test-junit4.

Android Kotlin by the numbers

  • 2,859 all-time installs (skills.sh)
  • +39 installs in the week ending Aug 4, 2026 (Skillselion tracking)
  • Ranked #64 of 1,039 Mobile Development skills by installs in the Skillselion catalog
  • Security screen: LOW risk (skills.sh audit)
  • Data as of Aug 5, 2026 (Skillselion catalog sync)
At a glance

android-kotlin capabilities & compatibility

Capabilities
compose ui and viewmodel state patterns · hilt dependency injection modules · room plus remote repository flow design · mockk and coroutines test setup
Use cases
frontend · testing
Platforms
macOS · Linux · Windows
From the docs

What android-kotlin says it does

Android Kotlin development with Coroutines, Jetpack Compose, Hilt, and MockK testing
SKILL.md
@HiltViewModel
SKILL.md
testImplementation("io.mockk:mockk:1.13.9")
SKILL.md
npx skills add https://github.com/alinaqi/claude-bootstrap --skill android-kotlin

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs2.9k
repo stars706
Security audit3 / 3 scanners passed
Last updatedJuly 14, 2026
Repositoryalinaqi/claude-bootstrap

How do I structure a modern Android Kotlin app with Compose UI, Hilt injection, Flow repositories, and testable ViewModels?

Build Android Kotlin apps with Jetpack Compose, Coroutines, Hilt DI, Room, and MockK-backed unit testing patterns.

Who is it for?

Developers building Jetpack Compose Android apps who need Hilt, Coroutines, Room, and MockK testing conventions.

Skip if: Skip for iOS SwiftUI work, backend-only Kotlin services, or legacy XML-only Android codebases.

When should I use this skill?

User edits Kotlin Android source, configures Gradle for Compose and Hilt, or implements ViewModel and repository Flow patterns.

What you get

Layered Android modules with Gradle dependencies, Compose screens, Hilt ViewModels, and Room-backed repository implementations.

  • Compose screen implementations
  • ViewModel and repository code
  • MockK unit tests

By the numbers

  • Documents 10 Kotlin anti-patterns for coroutines and Compose
  • Sample app targets compileSdk 34 with minSdk 24
  • CI workflow runs 4 Gradle checks: detekt, ktlintCheck, unit tests, assembleDebug

Files

SKILL.mdMarkdownGitHub ↗

Android Kotlin Skill

---

Project Structure

project/
├── app/
│   ├── src/
│   │   ├── main/
│   │   │   ├── kotlin/com/example/app/
│   │   │   │   ├── data/               # Data layer
│   │   │   │   │   ├── local/          # Room database
│   │   │   │   │   ├── remote/         # Retrofit/Ktor services
│   │   │   │   │   └── repository/     # Repository implementations
│   │   │   │   ├── di/                 # Hilt modules
│   │   │   │   ├── domain/             # Business logic
│   │   │   │   │   ├── model/          # Domain models
│   │   │   │   │   ├── repository/     # Repository interfaces
│   │   │   │   │   └── usecase/        # Use cases
│   │   │   │   ├── ui/                 # Presentation layer
│   │   │   │   │   ├── feature/        # Feature screens
│   │   │   │   │   │   ├── FeatureScreen.kt      # Compose UI
│   │   │   │   │   │   └── FeatureViewModel.kt
│   │   │   │   │   ├── components/     # Reusable Compose components
│   │   │   │   │   └── theme/          # Material theme
│   │   │   │   └── App.kt              # Application class
│   │   │   ├── res/
│   │   │   └── AndroidManifest.xml
│   │   ├── test/                       # Unit tests
│   │   └── androidTest/                # Instrumentation tests
│   └── build.gradle.kts
├── build.gradle.kts                    # Project-level build file
├── gradle.properties
├── settings.gradle.kts
└── CLAUDE.md

---

Gradle Configuration (Kotlin DSL)

App-level build.gradle.kts

plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
    id("com.google.dagger.hilt.android")
    id("com.google.devtools.ksp")
}

android {
    namespace = "com.example.app"
    compileSdk = 34

    defaultConfig {
        applicationId = "com.example.app"
        minSdk = 24
        targetSdk = 34
        versionCode = 1
        versionName = "1.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            isMinifyEnabled = true
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }

    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }

    kotlinOptions {
        jvmTarget = "17"
    }

    buildFeatures {
        compose = true
    }

    composeOptions {
        kotlinCompilerExtensionVersion = "1.5.8"
    }
}

dependencies {
    // Compose BOM
    val composeBom = platform("androidx.compose:compose-bom:2024.01.00")
    implementation(composeBom)
    implementation("androidx.compose.ui:ui")
    implementation("androidx.compose.ui:ui-tooling-preview")
    implementation("androidx.compose.material3:material3")
    implementation("androidx.activity:activity-compose:1.8.2")
    implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0")

    // Coroutines
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")

    // Hilt
    implementation("com.google.dagger:hilt-android:2.50")
    ksp("com.google.dagger:hilt-compiler:2.50")
    implementation("androidx.hilt:hilt-navigation-compose:1.1.0")

    // Room
    implementation("androidx.room:room-runtime:2.6.1")
    implementation("androidx.room:room-ktx:2.6.1")
    ksp("androidx.room:room-compiler:2.6.1")

    // Testing
    testImplementation("junit:junit:4.13.2")
    testImplementation("io.mockk:mockk:1.13.9")
    testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3")
    testImplementation("app.cash.turbine:turbine:1.0.0")
    androidTestImplementation("androidx.test.ext:junit:1.1.5")
    androidTestImplementation("androidx.compose.ui:ui-test-junit4")
    debugImplementation("androidx.compose.ui:ui-tooling")
    debugImplementation("androidx.compose.ui:ui-test-manifest")
}

---

Kotlin Coroutines & Flow

ViewModel with StateFlow

@HiltViewModel
class UserViewModel @Inject constructor(
    private val getUserUseCase: GetUserUseCase,
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {

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

    private val userId: String = checkNotNull(savedStateHandle["userId"])

    init {
        loadUser()
    }

    fun loadUser() {
        viewModelScope.launch {
            _uiState.update { it.copy(isLoading = true) }

            getUserUseCase(userId)
                .catch { e ->
                    _uiState.update {
                        it.copy(isLoading = false, error = e.message)
                    }
                }
                .collect { user ->
                    _uiState.update {
                        it.copy(isLoading = false, user = user, error = null)
                    }
                }
        }
    }

    fun clearError() {
        _uiState.update { it.copy(error = null) }
    }
}

data class UserUiState(
    val user: User? = null,
    val isLoading: Boolean = false,
    val error: String? = null
)

Repository with Flow

interface UserRepository {
    fun getUser(userId: String): Flow<User>
    fun observeUsers(): Flow<List<User>>
    suspend fun saveUser(user: User)
}

class UserRepositoryImpl @Inject constructor(
    private val api: UserApi,
    private val dao: UserDao,
    private val dispatcher: CoroutineDispatcher = Dispatchers.IO
) : UserRepository {

    override fun getUser(userId: String): Flow<User> = flow {
        // Emit cached data first
        dao.getUserById(userId)?.let { emit(it) }

        // Fetch from network and update cache
        val remoteUser = api.getUser(userId)
        dao.insert(remoteUser)
        emit(remoteUser)
    }.flowOn(dispatcher)

    override fun observeUsers(): Flow<List<User>> =
        dao.observeAllUsers().flowOn(dispatcher)

    override suspend fun saveUser(user: User) = withContext(dispatcher) {
        api.saveUser(user)
        dao.insert(user)
    }
}

---

Jetpack Compose

Screen with ViewModel

@Composable
fun UserScreen(
    viewModel: UserViewModel = hiltViewModel(),
    onNavigateBack: () -> Unit
) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()

    UserScreenContent(
        uiState = uiState,
        onRefresh = viewModel::loadUser,
        onErrorDismiss = viewModel::clearError,
        onNavigateBack = onNavigateBack
    )
}

@Composable
private fun UserScreenContent(
    uiState: UserUiState,
    onRefresh: () -> Unit,
    onErrorDismiss: () -> Unit,
    onNavigateBack: () -> Unit
) {
    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text("User Profile") },
                navigationIcon = {
                    IconButton(onClick = onNavigateBack) {
                        Icon(Icons.AutoMirrored.Filled.ArrowBack, "Back")
                    }
                }
            )
        }
    ) { padding ->
        Box(
            modifier = Modifier
                .fillMaxSize()
                .padding(padding)
        ) {
            when {
                uiState.isLoading -> {
                    CircularProgressIndicator(
                        modifier = Modifier.align(Alignment.Center)
                    )
                }
                uiState.user != null -> {
                    UserContent(user = uiState.user)
                }
            }

            uiState.error?.let { error ->
                Snackbar(
                    modifier = Modifier.align(Alignment.BottomCenter),
                    action = {
                        TextButton(onClick = onErrorDismiss) {
                            Text("Dismiss")
                        }
                    }
                ) {
                    Text(error)
                }
            }
        }
    }
}

---

Sealed Classes for State

Result Wrapper

sealed interface Result<out T> {
    data class Success<T>(val data: T) : Result<T>
    data class Error(val exception: Throwable) : Result<Nothing>
    data object Loading : Result<Nothing>
}

fun <T> Result<T>.getOrNull(): T? = (this as? Result.Success)?.data

inline fun <T, R> Result<T>.map(transform: (T) -> R): Result<R> = when (this) {
    is Result.Success -> Result.Success(transform(data))
    is Result.Error -> this
    is Result.Loading -> this
}

---

Testing with MockK & Turbine

ViewModel Tests

@OptIn(ExperimentalCoroutinesApi::class)
class UserViewModelTest {

    @get:Rule
    val mainDispatcherRule = MainDispatcherRule()

    private val getUserUseCase: GetUserUseCase = mockk()
    private val savedStateHandle = SavedStateHandle(mapOf("userId" to "123"))

    private lateinit var viewModel: UserViewModel

    @Before
    fun setup() {
        viewModel = UserViewModel(getUserUseCase, savedStateHandle)
    }

    @Test
    fun `loadUser success updates state with user`() = runTest {
        val user = User("123", "John Doe", "john@example.com")
        coEvery { getUserUseCase("123") } returns flowOf(user)

        viewModel.uiState.test {
            val initial = awaitItem()
            assertFalse(initial.isLoading)

            viewModel.loadUser()

            val loading = awaitItem()
            assertTrue(loading.isLoading)

            val success = awaitItem()
            assertFalse(success.isLoading)
            assertEquals(user, success.user)
        }
    }
}

class MainDispatcherRule(
    private val dispatcher: TestDispatcher = UnconfinedTestDispatcher()
) : TestWatcher() {
    override fun starting(description: Description) {
        Dispatchers.setMain(dispatcher)
    }
    override fun finished(description: Description) {
        Dispatchers.resetMain()
    }
}

---

GitHub Actions

name: Android Kotlin CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Setup Gradle
        uses: gradle/actions/setup-gradle@v3

      - name: Run Detekt
        run: ./gradlew detekt

      - name: Run Ktlint
        run: ./gradlew ktlintCheck

      - name: Run Unit Tests
        run: ./gradlew testDebugUnitTest

      - name: Build Debug APK
        run: ./gradlew assembleDebug

---

Lint Configuration

detekt.yml

build:
  maxIssues: 0

complexity:
  LongMethod:
    threshold: 20
  LongParameterList:
    functionThreshold: 4
  TooManyFunctions:
    thresholdInFiles: 10

style:
  MaxLineLength:
    maxLineLength: 120
  WildcardImport:
    active: true

coroutines:
  GlobalCoroutineUsage:
    active: true

---

Kotlin Anti-Patterns

  • Blocking coroutines on Main - Never use runBlocking on main thread
  • GlobalScope usage - Use structured concurrency with viewModelScope/lifecycleScope
  • Collecting flows in init - Use repeatOnLifecycle or collectAsStateWithLifecycle
  • Mutable state exposure - Expose StateFlow not MutableStateFlow
  • Not handling exceptions in flows - Always use catch operator
  • Lateinit for nullable - Use lazy or nullable with ?
  • Hardcoded dispatchers - Inject dispatchers for testability
  • Not using sealed classes - Prefer sealed for finite state sets
  • Side effects in Composables - Use LaunchedEffect/SideEffect
  • Unstable Compose parameters - Use stable/immutable types or @Stable

Related skills

Forks & variants (1)

Android Kotlin has 1 known copy in the catalog totaling 3 installs. They canonicalize to this original listing.

How it compares

Pick android-kotlin over generic Kotlin skills when you need opinionated Jetpack Compose, Hilt, and Room architecture templates for Android app modules.

FAQ

Which Android libraries does android-kotlin standardize?

Jetpack Compose, Hilt, Room, Kotlin Coroutines, Retrofit or Ktor remotes, and MockK with Turbine for tests.

What project structure does it recommend?

data, domain, di, and ui layers under app/src/main/kotlin with feature screens and reusable Compose components.

Is Android Kotlin safe to install?

skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.