Claude-skill-registry kotlin
Write Kotlin code for Android following best practices. Use when developing with Jetpack Compose, Android SDK, or Kotlin projects. Covers coroutines, state management, and tooling.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/kotlin" ~/.claude/skills/majiayu000-claude-skill-registry-kotlin && rm -rf "$T"
manifest:
skills/data/kotlin/SKILL.mdsource content
Kotlin / Android Development
Project Setup
Gradle Kotlin DSL
// settings.gradle.kts pluginManagement { repositories { google() mavenCentral() gradlePluginPortal() } } dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() } } rootProject.name = "MyApp" include(":app")
build.gradle.kts (app)
plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.compose) } android { namespace = "com.example.myapp" compileSdk = 35 defaultConfig { applicationId = "com.example.myapp" minSdk = 26 targetSdk = 35 versionCode = 1 versionName = "1.0" } buildFeatures { compose = true } compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { jvmTarget = "17" } } dependencies { implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.activity.compose) implementation(platform(libs.androidx.compose.bom)) implementation(libs.androidx.ui) implementation(libs.androidx.material3) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) }
Version Catalog (libs.versions.toml)
[versions] kotlin = "2.0.0" compose-bom = "2024.06.00" lifecycle = "2.8.0" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version = "1.13.1" } androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" } androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" } [plugins] android-application = { id = "com.android.application", version = "8.5.0" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
Type Patterns
Null Safety
// Safe call operator val length = name?.length // Elvis operator val displayName = user?.name ?: "Guest" // Safe cast val number = value as? Int // Not-null assertion (use sparingly) val name = user!!.name // let for null checks user?.let { safeUser -> println(safeUser.name) }
Sealed Classes
sealed class 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>() } // Exhaustive when fun handleResult(result: Result<User>) = when (result) { is Result.Success -> showUser(result.data) is Result.Error -> showError(result.exception) Result.Loading -> showLoading() }
Data Classes
data class User( val id: String, val email: String, val name: String, val createdAt: Instant = Instant.now() ) // Copy with modifications val updatedUser = user.copy(name = "New Name") // Destructuring val (id, email, name) = user
Value Classes
@JvmInline value class UserId(val value: String) @JvmInline value class Email(val value: String) { init { require(value.contains("@")) { "Invalid email" } } }
Error Handling
Result Type
fun parseNumber(input: String): Result<Int> { return try { Result.success(input.toInt()) } catch (e: NumberFormatException) { Result.failure(e) } } // Usage parseNumber("123") .onSuccess { number -> println("Parsed: $number") } .onFailure { error -> println("Error: ${error.message}") } // Transform val doubled = parseNumber("42") .map { it * 2 } .getOrDefault(0)
runCatching
val result = runCatching { riskyOperation() }.getOrElse { error -> logError(error) defaultValue } // Chain operations runCatching { fetchUser(id) } .mapCatching { user -> processUser(user) } .onSuccess { result -> display(result) } .onFailure { error -> showError(error) }
Coroutines
Basic Coroutines
// Suspend function suspend fun fetchUser(id: String): User { return withContext(Dispatchers.IO) { api.getUser(id) } } // Launch coroutine viewModelScope.launch { try { val user = fetchUser("123") _uiState.value = UiState.Success(user) } catch (e: Exception) { _uiState.value = UiState.Error(e.message) } }
Flow
// Create flow fun observeUsers(): Flow<List<User>> = flow { while (true) { emit(repository.getUsers()) delay(5000) } }.flowOn(Dispatchers.IO) // Collect flow viewModelScope.launch { observeUsers() .catch { e -> emit(emptyList()) } .collect { users -> _users.value = users } }
StateFlow
class UserViewModel : ViewModel() { private val _uiState = MutableStateFlow<UiState>(UiState.Loading) val uiState: StateFlow<UiState> = _uiState.asStateFlow() fun loadUser(id: String) { viewModelScope.launch { _uiState.value = UiState.Loading try { val user = repository.getUser(id) _uiState.value = UiState.Success(user) } catch (e: Exception) { _uiState.value = UiState.Error(e.message ?: "Unknown error") } } } }
Jetpack Compose
Basic Composable
@Composable fun Greeting(name: String, modifier: Modifier = Modifier) { Text( text = "Hello, $name!", modifier = modifier.padding(16.dp), style = MaterialTheme.typography.headlineMedium ) } @Preview(showBackground = true) @Composable fun GreetingPreview() { MyAppTheme { Greeting("Android") } }
State Management
@Composable fun Counter() { var count by remember { mutableIntStateOf(0) } Column( modifier = Modifier.padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally ) { Text( text = "Count: $count", style = MaterialTheme.typography.headlineMedium ) Spacer(modifier = Modifier.height(8.dp)) Button(onClick = { count++ }) { Text("Increment") } } }
ViewModel Integration
@Composable fun UserScreen( viewModel: UserViewModel = hiltViewModel() ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() when (val state = uiState) { is UiState.Loading -> CircularProgressIndicator() is UiState.Success -> UserContent(state.user) is UiState.Error -> ErrorMessage(state.message) } } @Composable fun UserContent(user: User) { Column(modifier = Modifier.padding(16.dp)) { Text(user.name, style = MaterialTheme.typography.titleLarge) Text(user.email, style = MaterialTheme.typography.bodyMedium) } }
Side Effects
@Composable fun UserDetailScreen(userId: String, viewModel: UserViewModel = hiltViewModel()) { // Run once when userId changes LaunchedEffect(userId) { viewModel.loadUser(userId) } // Run on every recomposition SideEffect { analytics.trackScreen("UserDetail") } // Cleanup when leaving composition DisposableEffect(Unit) { val listener = viewModel.addListener() onDispose { listener.remove() } } }
Testing
Unit Tests (JUnit 5)
class UserViewModelTest { @Test fun `loadUser updates state to success`() = runTest { val repository = mockk<UserRepository>() coEvery { repository.getUser("123") } returns User("123", "test@example.com") val viewModel = UserViewModel(repository) viewModel.loadUser("123") assertEquals( UiState.Success(User("123", "test@example.com")), viewModel.uiState.value ) } @Test fun `loadUser updates state to error on failure`() = runTest { val repository = mockk<UserRepository>() coEvery { repository.getUser(any()) } throws IOException("Network error") val viewModel = UserViewModel(repository) viewModel.loadUser("123") assertTrue(viewModel.uiState.value is UiState.Error) } }
Compose UI Tests
class UserScreenTest { @get:Rule val composeTestRule = createComposeRule() @Test fun displaysUserName() { val user = User("1", "test@example.com", "John Doe") composeTestRule.setContent { MyAppTheme { UserContent(user = user) } } composeTestRule.onNodeWithText("John Doe").assertIsDisplayed() composeTestRule.onNodeWithText("test@example.com").assertIsDisplayed() } @Test fun buttonClickIncrementsCounter() { composeTestRule.setContent { MyAppTheme { Counter() } } composeTestRule.onNodeWithText("Count: 0").assertIsDisplayed() composeTestRule.onNodeWithText("Increment").performClick() composeTestRule.onNodeWithText("Count: 1").assertIsDisplayed() } }
Tooling
# Build ./gradlew build ./gradlew assembleDebug ./gradlew assembleRelease # Run tests ./gradlew test # Unit tests ./gradlew connectedAndroidTest # Instrumented tests # Linting ./gradlew detekt # Code smells ./gradlew ktlintCheck # Style check ./gradlew ktlintFormat # Auto-fix style # Code analysis ./gradlew lint # Android Lint # Clean ./gradlew clean
detekt.yml
build: maxIssues: 0 complexity: LongMethod: threshold: 30 ComplexCondition: threshold: 4 style: MaxLineLength: maxLineLength: 120 WildcardImport: active: true naming: FunctionNaming: functionPattern: '[a-z][a-zA-Z0-9]*'
.editorconfig (ktlint)
[*.{kt,kts}] indent_size = 4 max_line_length = 120 ktlint_code_style = android_studio