.kiro/steering/kotlin-patterns.md
This file extends the common patterns with Kotlin and Android/KMP specific content.
val over var — default to val and only use var when mutation is requireddata class for value types; use immutable collections in public APIs!! — prefer ?., ?:, requireNotNull(), or checkNotNull()// BAD
val name = user!!.name
// GOOD
val name = user?.name ?: "Unknown"
Use sealed classes/interfaces to model closed state hierarchies:
sealed interface UiState<out T> {
data object Loading : UiState<Nothing>
data class Success<T>(val data: T) : UiState<T>
data class Error(val message: String) : UiState<Nothing>
}
Always use exhaustive when with sealed types — no else branch.
Single state object, event sink, one-way data flow:
data class ScreenState(
val items: List<Item> = emptyList(),
val isLoading: Boolean = false
)
class ScreenViewModel(private val useCase: GetItemsUseCase) : ViewModel() {
private val _state = MutableStateFlow(ScreenState())
val state = _state.asStateFlow()
fun onEvent(event: ScreenEvent) {
when (event) {
is ScreenEvent.Load -> load()
is ScreenEvent.Delete -> delete(event.id)
}
}
}
Single responsibility, operator fun invoke:
class GetItemUseCase(private val repository: ItemRepository) {
suspend operator fun invoke(id: String): Result<Item> {
return repository.getById(id)
}
}
Prefer constructor injection. Use Koin (KMP) or Hilt (Android-only):
// Koin
val dataModule = module {
single<ItemRepository> { ItemRepositoryImpl(get(), get()) }
factory { GetItemsUseCase(get()) }
viewModelOf(::ItemListViewModel)
}
viewModelScope in ViewModels, coroutineScope for structured child worksupervisorScope when child failures should be independentCancellationException — always rethrow itUse for platform-specific implementations:
// commonMain
expect fun platformName(): String
// androidMain
actual fun platformName(): String = "Android"
// iosMain
actual fun platformName(): String = "iOS"
BuildConfig or resources — values are extractable from the APKEncryptedSharedPreferences or Android Keystore (Android), Keychain (iOS), or a server-side proxy for runtime secretsnetwork_security_config.xml to block cleartext traffickotlin.test for multiplatform, JUnit for Android-specific testsrunTest with kotlinx-coroutines-test for coroutine testing@Test
fun `loading state emitted then data`() = runTest {
val repo = FakeItemRepository()
val viewModel = ItemListViewModel(GetItemsUseCase(repo))
viewModel.state.test {
assertEquals(ItemListState(), awaitItem())
viewModel.onEvent(ItemListEvent.Load)
assertTrue(awaitItem().isLoading)
}
}
See agents: kotlin-reviewer, kotlin-build-resolver for Kotlin-specific review and build error resolution.