Native Android Development in Kotlin
RecyclerView with DiffUtil.calculateDiff() on main thread, list of 500 elements, average Android phone from 2021 — and user gets 200–400 ms freeze on each data update. Move diff calculation to background thread through AsyncListDiffer — problem disappears. These things aren't obvious without profiler and understanding Android's threading model.
Kotlin + Jetpack Compose + Coroutines — current production standard for native Android development. XML and View system haven't disappeared, but new projects start with Compose.
Jetpack Compose: How Recomposition Works and Why It Matters
Compose — declarative UI framework. Instead of TextView.setText() and adapter.notifyItemChanged() — composable functions describing UI as state function. State changes trigger Compose to recompute only affected tree parts. This is recomposition.
Problem: recomposition can be too frequent. Pass lambda created on parent recomposition to child composable — child recomposes every time, even if visible data unchanged.
// Bad — new lambda on each recomposition, child thinks parameter changed
@Composable
fun ParentScreen(viewModel: MyViewModel = hiltViewModel()) {
val items by viewModel.items.collectAsState()
ItemList(
items = items,
onItemClick = { id -> viewModel.selectItem(id) } // created anew each time
)
}
// Good — remember stabilizes lambda
@Composable
fun ParentScreen(viewModel: MyViewModel = hiltViewModel()) {
val items by viewModel.items.collectAsState()
val onItemClick = remember { { id: String -> viewModel.selectItem(id) } }
ItemList(items = items, onItemClick = onItemClick)
}
Stability and @Stable/@Immutable
Compose determines whether to recompose composable by checking parameter stability. Type is stable if Compose can guarantee: if two values equal by equals(), their UI representation identical.
Primitives, String, data classes with val stable-type fields — stable automatically. List<T> — unstable, because it's interface. MutableList can change without notification. Solution — ImmutableList from kotlinx.collections.immutable or @Immutable annotation on data class.
LazyColumn and List Performance
LazyColumn — RecyclerView equivalent in Compose. key in items { } — mandatory for any list where elements move or delete. Without key, Compose can't distinguish element movement from deletion+addition, breaking animations and potentially losing cell state.
contentType — additional optimization. Multiple cell types allow Compose to reuse composition for same-type cells. This parallels getItemViewType in RecyclerView.
Kotlin Coroutines and Architecture
Coroutines aren't just "convenient async code." This is structured concurrency with clear scope and lifecycle.
viewModelScope — coroutine scope tied to ViewModel lifecycle. When ViewModel clears (onCleared()), all scope coroutines automatically cancel. This eliminates entire leak class typical for callback approaches.
Flow vs LiveData
StateFlow and SharedFlow — recommended LiveData replacements on Kotlin projects. LiveData is lifecycle-aware but tied to Android platform. Flow — pure Kotlin, testable without Android dependencies.
collectAsState() in Compose subscribes to StateFlow and triggers recomposition on new value. lifecycleScope.launch { flow.collect { } } — for assembly in Fragment or Activity with lifecycle through repeatOnLifecycle(Lifecycle.State.STARTED).
repeatOnLifecycle matters. Without it, stream collects even in background, potentially processing UI events when inactive.
Dispatcher and Structured Concurrency
Dispatchers.IO — network and file operations. Dispatchers.Default — CPU-intensive tasks (parsing, sorting, encryption). Dispatchers.Main — UI.
withContext(Dispatchers.IO) switches coroutine to dispatcher without creating new scope. More efficient than launch(Dispatchers.IO) inside another launch.
Hilt and Dependency Injection
Hilt — official DI framework for Android over Dagger 2. Eliminates Dagger boilerplate: no Component writing, no manual Module linking.
@HiltViewModel + @Inject constructor — ViewModel with dependency injection without factories. @Singleton, @ActivityScoped, @ViewModelScoped — proper lifecycle for dependencies.
Common mistake: use @Singleton for repository holding Activity context. This leaks Activity. Rule: @Singleton only for dependencies needing Application context or not holding Android-specific state.
WorkManager and Background Tasks
WorkManager — for guaranteed background tasks completing even after app restart or device reboot. Data sync, analytics upload, file downloads.
CoroutineWorker — suspend version of Worker. Works in Dispatchers.IO by default.
Android 14 tightened background execution. FOREGROUND_SERVICE_TYPE became mandatory for foreground services. WorkManager correctly handles constraints (network, charging) without requiring foreground service for most tasks.
Tools
Android Studio Profiler. CPU profiler with System Trace — everything visible: coroutine suspension points, RenderThread, MainThread. Memory profiler — heap dump, allocation tracking. Network profiler — all HTTP requests with bodies.
Compose Layout Inspector. Composable tree showing recomposition count. Visible which composables recompose too frequently — more precise than any logging.
LeakCanary. Automatic memory leak detection in development build. Shows reference chain to leak. Adds one dependency, works without configuration.
Firebase Crashlytics + Performance Monitoring. Crash-free rate by versions, Network request traces, Custom traces for critical operations.
Timelines
| Complexity | Estimated Timeline |
|---|---|
| MVP (6–10 screens, REST API) | 6–10 weeks |
| Medium app (20–30 screens) | 3–5 months |
| Complex (payments, ML Kit, Compose + custom UI) | 5–9 months |
Cost calculated after requirements and TZ analysis.







