Idiomatic Kotlin patterns for Android development — coroutines, Flow, Jetpack Compose, JUnit/Espresso testing, ktlint, and Gradle build commands. Use when working on Android or Kotlin JVM projects.
Idiomatic Kotlin conventions for Android projects, covering coroutines, Compose, testing, and build toolchain.
// ✓ Use ?: for defaults, ?.let for nullable chains
val name = user?.name ?: "Unknown"
user?.let { sendEmail(it.email) }
// ✗ Avoid !! in production
val name = user!!.name
// ✓ Value objects as data classes
data class User(val id: UUID, val name: String, val email: String)
// ✓ Copy for immutable updates
val updated = user.copy(name = "Bob")
// ✓ Structured concurrency
class UserRepository(private val api: UserApi) {
suspend fun getUser(id: UUID): User = withContext(Dispatchers.IO) {
api.fetchUser(id)
}
}
// ✓ Flow for reactive streams
fun userUpdates(): Flow<User> = flow {
while (true) {
emit(api.fetchUser(userId))
delay(30_000)
}
}
@Composable
fun UserCard(user: User, modifier: Modifier = Modifier) {
Card(modifier = modifier) {
Column(modifier = Modifier.padding(16.dp)) {
Text(text = user.name, style = MaterialTheme.typography.headlineSmall)
Text(text = user.email, style = MaterialTheme.typography.bodyMedium)
}
}
}
class UserRepositoryTest {
private val api = mockk<UserApi>()
private val repo = UserRepository(api)
@Test
fun `getUser returns user from api`() = runTest {
val expected = User(UUID.randomUUID(), "Alice", "[email protected]")
coEvery { api.fetchUser(any()) } returns expected
val result = repo.getUser(expected.id)
assertEquals(expected, result)
}
}
@RunWith(AndroidJUnit4::class)
class UserScreenTest {
@get:Rule
val rule = ActivityScenarioRule(MainActivity::class.java)
@Test
fun displaysUserName() {
onView(withId(R.id.user_name)).check(matches(withText("Alice")))
}
}
Run tests:
./gradlew test # unit tests
./gradlew connectedAndroidTest # instrumented tests (requires device/emulator)
./gradlew testDebugUnitTest # debug variant only
./gradlew assembleDebug # debug APK
./gradlew assembleRelease # release APK
./gradlew bundleRelease # AAB for Play Store
./gradlew clean # clean build outputs
./gradlew dependencies # show dependency tree
ktlint # lint
ktlint --format # auto-fix
ktlint "src/**/*.kt" # specific files
| Pitfall | Fix |
|---|---|
GlobalScope.launch | Use viewModelScope / lifecycleScope |
runBlocking in Android main thread | Use lifecycleScope.launch |
Leaking Context in ViewModel | Never pass Activity to ViewModel |
Missing remember in Compose | Wrap state with remember { mutableStateOf(...) } |
| Hardcoded strings | Use strings.xml resource |
rules/kotlin/coding-style.md