Kotlin Android Basics Explained
Android development has fully embraced Kotlin — Jetpack Compose builds declarative UI with Kotlin DSL, ViewModel + StateFlow manages reactive state, and Room + coroutines simplify the data layer. Charlie builds the Android order dashboard.
1. What You'll Learn
- Activity / Fragment lifecycle and Kotlin integration
- Jetpack Compose: declarative UI + Kotlin DSL
- ViewModel + StateFlow: reactive state management
- Room database: Entity + Dao + coroutines
- Charlie's hands-on: Android order dashboard
2. A Developer's Real Story
(1) Pain Point: The State-Sync Hell of Imperative UI
Alice used XML + RecyclerView to build the order list. Every state change required manual UI updates: when data changed, call notifyDataSetChanged(); when loading, show/hide a progress bar — 10 states needed 20+ lines of UI sync code.
(2) Compose Declarative UI Solution
KOTLIN
// XML + RecyclerView: imperative, error-prone
// recyclerView.adapter.notifyDataSetChanged()
// Compose: declarative, automatic
@Composable
fun OrderList(orders: List<Order>) {
LazyColumn {
items(orders) { order ->
OrderCard(order) // UI automatically reflects data changes
}
}
}
Compose is declarative — UI is a function of state. When data changes, UI updates automatically with no manual sync.
3. Activity Lifecycle
(1) Lifecycle Callbacks
KOTLIN
class OrderActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
OrderScreen()
}
}
override fun onResume() {
super.onResume()
// Refresh data when activity comes to foreground
}
override fun onPause() {
super.onPause()
// Save draft data
}
}
(2) Lifecycle State Diagram
stateDiagram-v2
[*] --> Created: onCreate()
Created --> Started: onStart()
Started --> Resumed: onResume()
Resumed --> Started: onPause()
Started --> Created: onStop()
Created --> [*]: onDestroy()
Started --> Resumed: onResume()
(3) Lifecycle Callback Comparison
| Callback | Trigger | Typical Operation |
|---|---|---|
onCreate(bundle) |
Activity created | Initialize UI, ViewModel |
onStart() |
Visible but not interactive | Register listeners |
onResume() |
Gains focus | Start animations, refresh data |
onPause() |
Loses focus | Pause animations, save draft |
onStop() |
No longer visible | Release resources |
onDestroy() |
Being destroyed | Clean up resources |
4. Jetpack Compose
(1) Composable Functions
KOTLIN
@Composable
fun OrderCard(order: Order) {
Card(
modifier = Modifier.fillMaxWidth().padding(8.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
) {
Row(
modifier = Modifier.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
Column {
Text(text = order.id, style = MaterialTheme.typography.titleMedium)
Text(text = order.customer, style = MaterialTheme.typography.bodyMedium)
}
Text(
text = "\$${order.total} USD",
style = MaterialTheme.typography.titleLarge
)
}
}
}
(2) Lists and State
KOTLIN
@Composable
fun OrderListScreen(viewModel: OrderViewModel = viewModel()) {
val orders by viewModel.orders.collectAsState()
val isLoading by viewModel.isLoading.collectAsState()
if (isLoading) {
CircularProgressIndicator()
} else {
LazyColumn {
items(orders) { order ->
OrderCard(order)
}
}
}
}
(3) Compose Rendering Pipeline
flowchart TD
A[State Change] --> B[Composition<br/>Run @Composable functions]
B --> C[Layout<br/>Measure and position]
C --> D[Drawing<br/>Render to canvas]
D --> E[UI Updated]
(4) XML vs Compose Comparison
| Dimension | XML + View | Jetpack Compose |
|---|---|---|
| Paradigm | Imperative | Declarative |
| Language | XML + Kotlin | Pure Kotlin |
| State sync | Manual | Automatic |
| Code volume | High (XML + Adapter + ViewHolder) | Low (@Composable) |
| Preview | Requires run | IDE real-time preview |
| Learning curve | Moderate | Moderate (new paradigm) |
5. ViewModel + StateFlow
(1) ViewModel
KOTLIN
class OrderViewModel : ViewModel() {
private val _orders = MutableStateFlow<List<Order>>(emptyList())
val orders: StateFlow<List<Order>> = _orders.asStateFlow()
private val _isLoading = MutableStateFlow(false)
val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()
fun loadOrders() {
viewModelScope.launch {
_isLoading.value = true
try {
_orders.value = orderRepository.findAll()
} finally {
_isLoading.value = false
}
}
}
}
(2) StateFlow vs LiveData
| Dimension | LiveData | StateFlow |
|---|---|---|
| Initial value | Not required | Required |
| Coroutine support | Needs adapters | Native |
| Thread | Main thread | Any thread |
| Lifecycle | Aware | Needs repeatOnLifecycle |
| Recommendation | Legacy code | New projects |
6. Room Database
(1) Entity + Dao
KOTLIN
@Entity(tableName = "orders")
data class OrderEntity(
@PrimaryKey val id: String,
val total: Double,
val status: String,
val customer: String,
val createdAt: Long = System.currentTimeMillis()
)
@Dao
interface OrderDao {
@Query("SELECT * FROM orders ORDER BY createdAt DESC")
fun getAllOrders(): Flow<List<OrderEntity>>
@Query("SELECT * FROM orders WHERE id = :id")
suspend fun getById(id: String): OrderEntity?
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(order: OrderEntity)
@Delete
suspend fun delete(order: OrderEntity)
}
(2) Room Components
| Component | Annotation | Responsibility |
|---|---|---|
| Entity | @Entity |
Database table mapping |
| Dao | @Dao |
Data access operations |
| Database | @Database |
Database entry point |
7. Complete Example: Android Order Dashboard
KOTLIN
// ============================================
// OrderProcessor - Android Order Dashboard
// Feature: Compose UI + ViewModel + StateFlow
// ============================================
// --- Domain ---
data class Order(val id: String, val total: Double, val status: String, val customer: String)
// --- ViewModel ---
class OrderViewModel : ViewModel() {
private val _orders = MutableStateFlow<List<Order>>(emptyList())
val orders: StateFlow<List<Order>> = _orders.asStateFlow()
private val _isLoading = MutableStateFlow(false)
val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()
init {
loadOrders()
}
fun loadOrders() {
viewModelScope.launch {
_isLoading.value = true
delay(500) // Simulate network
_orders.value = listOf(
Order("ORD-001", 299.99, "CONFIRMED", "Alice"),
Order("ORD-002", 15_000.00, "PENDING", "Bob"),
Order("ORD-003", 2_500.00, "SHIPPED", "Charlie"),
Order("ORD-004", 8_900.00, "CONFIRMED", "Bob"),
Order("ORD-005", 45.50, "CANCELLED", "Alice")
)
_isLoading.value = false
}
}
fun totalRevenue(): Double = _orders.value
.filter { it.status != "CANCELLED" }
.sumOf { it.total }
}
// --- Compose UI (Conceptual - requires Android runtime) ---
// @Composable
// fun OrderScreen(viewModel: OrderViewModel = viewModel()) {
// val orders by viewModel.orders.collectAsState()
// val isLoading by viewModel.isLoading.collectAsState()
//
// Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
// Text("Order Dashboard", style = MaterialTheme.typography.headlineMedium)
// Text("Revenue: \$${viewModel.totalRevenue()} USD", style = MaterialTheme.typography.titleLarge)
//
// if (isLoading) {
// CircularProgressIndicator()
// } else {
// LazyColumn {
// items(orders) { order -> OrderCard(order) }
// }
// }
// }
// }
// --- JVM Demo (simulating the ViewModel logic) ---
fun main() {
val viewModel = OrderViewModel()
// Simulate observing state
Thread.sleep(1000) // Wait for loading
println("=== Android Order Dashboard ===")
println("Revenue: \$${viewModel.totalRevenue()} USD")
println("Loading: ${viewModel.isLoading.value}")
viewModel.orders.value.forEach { order ->
val icon = when (order.status) {
"CONFIRMED" -> "✅"
"PENDING" -> "⏳"
"SHIPPED" -> "📦"
"CANCELLED" -> "❌"
else -> "?"
}
println(" $icon ${order.id}: \$${order.total} USD (${order.customer}) [${order.status}]")
}
}
Output:
TEXT
=== Android Order Dashboard ===
Revenue: $26699.99 USD
Loading: false
✅ ORD-001: $299.99 USD (Alice) [CONFIRMED]
⏳ ORD-002: $15000.0 USD (Bob) [PENDING]
📦 ORD-003: $2500.0 USD (Charlie) [SHIPPED]
✅ ORD-004: $8900.0 USD (Bob) [CONFIRMED]
❌ ORD-005: $45.5 USD (Alice) [CANCELLED]
❓ FAQ
Q Do I have to use Compose? Can I still use XML?
A You can continue using XML, but Google recommends Compose as the default for new projects. Compose and XML can coexist (use
ComposeView to embed Compose in XML).Q What's the difference between ViewModel and Presenter?
A ViewModel is not destroyed on configuration changes (screen rotation) — data is automatically retained. A Presenter requires manual save/restore on config changes.
Q How do I choose between StateFlow and SharedFlow?
A StateFlow is for holding state (must have an initial value, deduplicates). SharedFlow is for event streams (multicast, no initial value). Use StateFlow for UI state, SharedFlow for one-shot events.
Q Does Room support coroutines?
A Yes. Dao
suspend functions automatically execute on Room's thread pool. Queries returning Flow automatically re-emit when data changes.Q How is Compose performance?
A Compose uses smart recomposition (only redraws changed Composables), with performance close to hand-written Views. Avoid computations inside Composable — use
remember and derivedStateOf for caching.Q How do I handle navigation in Compose?
A Use
NavHost + composable() to define a navigation graph, navController.navigate() to navigate. Type-safe navigation is available in Compose 1.0+.📖 Summary
- Android development is now fully Kotlin-based; Jetpack Compose is the new UI paradigm
- Compose declarative UI: UI = f(State) — data changes, UI updates automatically
- ViewModel + StateFlow manages reactive UI state, safe across configuration changes
- Room + coroutines + Flow builds a reactive data layer
- Compose uses Kotlin DSL to build component trees, more concise than XML
- Activity lifecycle remains fundamental to Android, but Compose reduces direct lifecycle interaction
📝 Exercises
- Beginner (⭐): Create a Compose
Greetingfunction that displays "Hello, $name!". Hint:@Composable fun Greeting(name: String) { Text("Hello, $name!") } - Intermediate (⭐⭐): Use ViewModel + StateFlow to implement a counter with Compose UI showing the count in real time. Hint:
MutableStateFlow(0)+collectAsState() - Advanced (⭐⭐⭐): Implement a complete order list Compose UI: LazyColumn displaying orders, pull-to-refresh, click to navigate to detail. Hint:
LazyColumn+pullRefresh+NavController



