Thanks to visit codestin.com
Credit goes to www.scribd.com

0% found this document useful (0 votes)
10 views10 pages

Android Kotlin Handbook Senior NonCompose

The Android Kotlin Handbook is a detailed guide for senior-level engineers focusing on Kotlin for Android development, covering essential topics like language mastery, architecture, concurrency, testing, and security, specifically for XML UI without Jetpack Compose. It provides structured sections for practical application, including concurrency with coroutines, advanced Android APIs, and best practices. The handbook emphasizes idiomatic Kotlin usage, performance optimization, and thorough testing methodologies to ensure robust Android applications.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
10 views10 pages

Android Kotlin Handbook Senior NonCompose

The Android Kotlin Handbook is a detailed guide for senior-level engineers focusing on Kotlin for Android development, covering essential topics like language mastery, architecture, concurrency, testing, and security, specifically for XML UI without Jetpack Compose. It provides structured sections for practical application, including concurrency with coroutines, advanced Android APIs, and best practices. The handbook emphasizes idiomatic Kotlin usage, performance optimization, and thorough testing methodologies to ensure robust Android applications.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 10

Android Kotlin Handbook — Senior-

Level (Non-Compose)
A comprehensive, Android-focused Kotlin guide for 10+ years experienced engineers.
Covers language mastery, Android architecture, concurrency, testing, security,
interoperability, performance, and real-world patterns. XML (View-based) UI only; no
Jetpack Compose content.

How to Use This Handbook


 Use Part 1 for Kotlin language mastery in Android contexts.
 Use Parts 2–4 for concurrency, data, WorkManager, and platform APIs.
 Use Parts 5–7 for interoperability, build optimization, security, and testing.
 Skim anti-patterns and checklists before code reviews and performance audits.

Fig 1. MVVM with StateFlow in a View-based Android app.


Part 1 — Kotlin Core for Android

1. Null-Safety in Android Contexts


Android APIs return nullable types frequently (e.g., findViewById<T>(),
getSystemService()). Prefer safe-calls and early returns. Avoid !! except at module
boundaries with strong invariants.
// Activity/Fragment example
val toolbar: Toolbar? = findViewById(R.id.toolbar)
toolbar?.let { setSupportActionBar(it) } ?: run {
Log.w("UI", "Toolbar missing; skipping setup")
}

2. Data Classes for Parcelable Models


Use @Parcelize to reduce boilerplate; keep models immutable.
@Parcelize
data class User(
val id: Long,
val name: String,
val email: String?
) : Parcelable

3. Inline Classes for Domain Types


Inline classes reduce allocation and enforce strong typing (e.g., UserId, Email).
@JvmInline
value class UserId(val raw: Long)

@JvmInline
value class Email(val value: String) {
init { require('@' in value) }
}

4. Advanced Generics & Variance for Android


Use variance for producer/consumer APIs and RecyclerView abstractions.
interface Mapper<in I, out O> { fun map(input: I): O }

class UserDtoToUi : Mapper<UserDto, UserUi> {


override fun map(input: UserDto) = UserUi(input.id, input.name)
}

Star projections when you must accept unknown types (rare, prefer type parameters).

5. Delegation Patterns
Use class/property delegation to reuse behavior across Activities/Fragments/ViewModels.
interface Logger { fun log(msg: String) }
class AndroidLogger : Logger { override fun log(msg: String) = Log.d("APP", msg)
}
class Repo(private val api: Api, logger: Logger) : Logger by logger {
suspend fun load() { log("loading…"); /* … */ }
}

Property delegates: lazy, observable, and preference-backed.


class Settings(context: Context) {
private val prefs = context.getSharedPreferences("s", Context.MODE_PRIVATE)
var featureEnabled: Boolean by BooleanPreference(prefs, "f_enabled", false)
}

6. Extension & Higher-Order Functions


Use extensions to encapsulate Android quirks; higher-order functions for callbacks.
inline fun <T> Fragment.requireArgument(key: String): T =
requireNotNull(arguments?.get(key) as? T) { "Missing $key" }

7. Collections & Sequences Performance


Prefer sequences for large pipelines to avoid intermediate lists on the main thread.
val names = users.asSequence()
.filter { it.active }
.map { it.name.trim() }
.sorted() // terminal op; executed lazily
.toList()

Part 2 — Concurrency & Coroutines

8. Structured Concurrency with ViewModelScope


Tie jobs to lifecycle; cancel onCleared to prevent leaks/ANRs.
class UsersViewModel(
private val repo: UsersRepo
) : ViewModel() {

private val _state = MutableStateFlow<List<UserUi>>(emptyList())


val state = _state

fun refresh() = viewModelScope.launch {


val users = withContext(Dispatchers.IO) { repo.loadUsers() }
_state.value = users
}
}

9. Exception Handling: SupervisorJob & CoroutineExceptionHandler


Use SupervisorJob to isolate failures. Log with CoroutineExceptionHandler.
val handler = CoroutineExceptionHandler { _, e -> Log.e("VM", "error", e) }
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate +
handler)
10. Flow vs LiveData in Legacy UI
Prefer Flow/StateFlow; use asLiveData() only when interop with existing code is
mandatory.
lifecycleScope.launchWhenStarted {
viewModel.state.collect { render(it) }
}

11. Hot Flows: SharedFlow & StateFlow


SharedFlow for one-off events; StateFlow for state. Avoid SingleLiveEvent hacks.
private val _events = MutableSharedFlow<UiEvent>(extraBufferCapacity = 1)
val events = _events.asSharedFlow()

suspend fun showToast(msg: String) { _events.emit(UiEvent.Toast(msg)) }

12. Backpressure & Main-Safety


Throttle UI updates; use distinctUntilChanged; switch to IO for heavy ops.
viewModel.state
.distinctUntilChanged()
.flowOn(Dispatchers.Default)
.onEach { render(it) }
.launchIn(lifecycleScope)

Fig 2. Structured concurrency: sibling jobs fail independently under SupervisorJob.


Part 3 — Android Architecture & Patterns

13. MVVM with Events & State


Represent view state explicitly; model events as sealed classes.
data class UiState(
val loading: Boolean = false,
val items: List<Item> = emptyList(),
val error: String? = null
)

sealed interface UiEvent {


data object Retry : UiEvent
data class ClickItem(val id: Long) : UiEvent
}

14. Clean Architecture Layers


Use cases orchestrate; repositories abstract data; avoid Android types in domain.
class GetItems(private val repo: ItemsRepo) {
suspend operator fun invoke(): List<Item> = repo.items()
}
Fig 3. Clean Architecture separation with Android constraints.

15. Dependency Injection (Dagger/Hilt)


Prefer Hilt for Android integration; use assisted injection for runtime args.
@HiltViewModel
class DetailViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
private val repo: Repo
) : ViewModel() {
private val id: Long = savedStateHandle["id"]!!
}

16. Repository & Mapper Patterns


Keep DTOs out of UI; map in a single place; prefer pure functions for mapping.
class ItemDtoToUi : Mapper<ItemDto, ItemUi> {
override fun map(input: ItemDto) = ItemUi(
id = input.id,
title = input.title ?: "Untitled"
)
}

Part 4 — Advanced Android APIs (XML UI)

17. Room + Coroutines + Flow


Expose Flow from DAO; transform on Dispatchers.Default; collect on Main.
@Dao
interface UserDao {
@Query("SELECT * FROM users ORDER BY name")
fun users(): Flow<List<UserEntity>>
}

TypeConverters for inline classes and complex types.


@TypeConverter
fun emailFromString(raw: String?): Email? = raw?.let(::Email)

18. WorkManager with Kotlin


Chain works; pass data safely; observe with LiveData or Flow.
val request = OneTimeWorkRequestBuilder<SyncWorker>()
.setInputData(workDataOf("userId" to userId.value))
.build()

WorkManager.getInstance(context).enqueue(request)

19. Networking: Retrofit + OkHttp + Kotlin


Use suspend functions in Retrofit interfaces; add interceptors for auth/logging.
interface Api {
@GET("users")
suspend fun users(): List<UserDto>
}

20. Bluetooth & BLE with Coroutines


Wrap callbacks into suspend/Flow; respect permissions and scanning windows.
suspend fun BluetoothGatt.awaitConnection(): BluetoothGatt =
suspendCancellableCoroutine { cont ->
val cb = object : BluetoothGattCallback() {
override fun onConnectionStateChange(gatt: BluetoothGatt, status:
Int, newState: Int) {
if (newState == BluetoothProfile.STATE_CONNECTED)
cont.resume(gatt) {}
}
}
// connectGatt(..., cb)
}

Fig 4. Flow pipeline from Room to ViewModel with transformation dispatcher.

Part 5 — Interoperability, Build & Optimization

21. Java Interop


SAM conversions, @JvmOverloads, @JvmStatic, and @file:JvmName for clean APIs.
class ImageLoader @JvmOverloads constructor(
private val cache: Cache,
private val network: Network = DefaultNetwork()
) {
@JvmStatic fun version() = 1
}

22. Gradle (Groovy) Tips


Keep modules small; enable configuration on demand where appropriate; cache with Gradle
Enterprise if available.
// build.gradle (app)
android {
compileSdkVersion 35
defaultConfig {
minSdkVersion 24
targetSdkVersion 35
}
buildFeatures { viewBinding true }
}

23. ProGuard/R8 Rules for Kotlin


Keep reflective usages; annotate with @Keep for entry points; keep Kotlin metadata when
needed.
-keep class kotlinx.** { *; }
-keep class kotlin.** { *; }
-keepattributes *Annotation*

24. Performance Patterns


 Avoid allocations in tight draw/layout paths; reuse objects.
 Use inline + value classes for small wrappers.
 Prefer immutable models and diffing in RecyclerView.
 Move heavy work off the main thread; measure with Systrace/Perfetto.

25. Memory & Leak Prevention


 Never hold long-lived references to Views/Contexts in ViewModels.
 Cancel coroutines in onStop/onDestroyView.
 Use WeakReference for callbacks tied to Views if necessary.

Part 6 — Security & Testing

26. Permissions & Security


Model permission state as a sealed hierarchy; verify at runtime; defer flows until granted.
sealed interface PermissionState {
data object Granted : PermissionState
data class Denied(val permanently: Boolean) : PermissionState
}
27. Unit Testing (JUnit5, MockK, Coroutines)
Use runTest; inject Dispatchers via CoroutineDispatcherProvider.
@Test
fun `loads users`() = runTest {
val repo = mockk<UsersRepo> { coEvery { loadUsers() } returns
listOf(UserUi(1,"A")) }
val vm = UsersViewModel(repo)
vm.refresh()
assertEquals(listOf(UserUi(1,"A")), vm.state.value)
}

28. UI Testing (Espresso)


Prefer idling resources or coroutines test dispatchers to avoid flakiness.
onView(withId(R.id.button_retry)).perform(click())
onView(withText("Loaded")).check(matches(isDisplayed()))

29. Observability & Debugging Coroutines


Add coroutine name; log transitions; expose diagnostic toggles in debug builds.
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main +
CoroutineName("UsersVM"))

Part 7 — Best Practices, Pitfalls & Checklists

30. Idiomatic Kotlin in Android


 Prefer expression bodies and val over var.
 Prefer sealed hierarchies over enums with data.
 Avoid platform types leaking into domain.

31. Scope Functions: When & Why


Use apply for builders; let for nullable chains; also for side-effects; run for single-expression
blocks.
val dialog = AlertDialog.Builder(context).apply {
setTitle("Title")
setMessage("Message")
}.create()

32. Anti-Patterns
 SingleLiveEvent hacks for events; use SharedFlow instead.
 Doing IO on Main; use withContext(Dispatchers.IO).
 ViewModel keeping View/Fragment references.
 Leaking coroutines from adapters or custom views.
33. Migration from Legacy Java
Migrate gradually: module by module; keep interop clean; replace Rx with Flow via
adapters where needed.
// Rx -> Flow adapter (simplified)
fun <T: Any> Observable<T>.asFlow(): Flow<T> = callbackFlow {
val d = subscribe({ trySend(it) }, { close(it) }, { close() })
awaitClose { d.dispose() }
}

Appendix A — Threading & Dispatchers Cheat Sheet


 Main: UI updates only; never block.
 Default: CPU-bound transformations and reducers.
 IO: File/network/db operations.
 Unconfined: advanced; avoid in UI code.

Appendix B — Code Review Checklist (Android + Kotlin)


1. Are coroutines lifecycle-aware and cancelable?
2. Is Flow used instead of SingleLiveEvent for UI events?
3. Are repositories pure and side-effect free (except IO boundaries)?
4. Are mappers tested and isolated?
5. Any reflection requiring keep rules?
6. Is null-handling explicit and safe?

You might also like