Bobby Encoded
PostsAbout
PostsAbout

© 2026 Bobby Jose

← Back to Blog

Mastering Jetpack Compose Foundations

October 1, 2025 · 6 min read

Android, Kotlin, Jetpack Compose, Mobile Development, Interview Prep

Part 1 of the Android Deep Dive series

Jetpack Compose has become the de facto standard for Android UI development. In 2025-2026, virtually every Android interview expects deep Compose knowledge. With KMP (Kotlin Multiplatform) reaching enterprise readiness and Compose Multiplatform gaining momentum, understanding Compose fundamentals is more critical than ever.

Why Compose Matters in 2025-2026

According to recent developer surveys, Compose adoption has exploded. Google has made Compose the default for modern Android development, with major libraries like Room, DataStore, and ViewModel now offering KMP support. Interviewers expect candidates to demonstrate not just familiarity, but mastery of declarative UI patterns.

Industry Trend

Companies report reducing UI code by 30-50% after migrating to Compose. Interviewers often ask about migration strategies and when XML views are still appropriate.

Core Concepts

Composable Functions

Composables are functions annotated with @Composable that describe UI. They emit UI elements rather than returning values.

@Composable
fun NutritionCard(
    title: String,
    calories: Int,
    modifier: Modifier = Modifier
) {
    Card(
        modifier = modifier.fillMaxWidth(),
        colors = CardDefaults.cardColors(
            containerColor = MaterialTheme.colorScheme.surfaceVariant
        )
    ) {
        Column(
            modifier = Modifier.padding(16.dp),
            verticalArrangement = Arrangement.spacedBy(8.dp)
        ) {
            Text(
                text = title,
                style = MaterialTheme.typography.headlineSmall
            )
            Text(
                text = "$calories kcal",
                style = MaterialTheme.typography.bodyMedium,
                color = MaterialTheme.colorScheme.onSurfaceVariant
            )
        }
    }
}

Modifiers: Order Matters

Modifiers configure size, layout, behavior, and appearance. They're applied as a chain wrapping the content - order is critical.

// Padding then background: transparent padding, red content
Box(
    modifier = Modifier
        .padding(16.dp)
        .background(Color.Red)
)

// Background then padding: red extends under padding
Box(
    modifier = Modifier
        .background(Color.Red)
        .padding(16.dp)
)
// These produce DIFFERENT results!

Interview Tip

Interviewers love asking about modifier order. Think of it as nested boxes - each modifier wraps what came before.

Common Modifier Patterns

// Size modifiers
Modifier
    .size(100.dp)           // Fixed size
    .fillMaxWidth()         // Full parent width
    .fillMaxSize()          // Full parent size
    .wrapContentSize()      // Wrap content

// Spacing
Modifier
    .padding(16.dp)         // All sides
    .padding(horizontal = 8.dp, vertical = 16.dp)

// Appearance
Modifier
    .background(Color.Blue, RoundedCornerShape(8.dp))
    .clip(CircleShape)
    .border(1.dp, Color.Gray, RoundedCornerShape(8.dp))

// Interaction
Modifier
    .clickable { /* handle click */ }
    .weight(1f)  // For Row/Column children

Layout Composables

// Column - Vertical arrangement
@Composable
fun VerticalMealList() {
    Column(
        modifier = Modifier.fillMaxWidth(),
        verticalArrangement = Arrangement.spacedBy(8.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text("Breakfast")
        Text("Lunch")
        Text("Dinner")
    }
}

// Row - Horizontal arrangement
@Composable
fun MacroRow() {
    Row(
        modifier = Modifier.fillMaxWidth(),
        horizontalArrangement = Arrangement.SpaceBetween,
        verticalAlignment = Alignment.CenterVertically
    ) {
        Text("Calories")
        Spacer(modifier = Modifier.weight(1f))
        Text("1,200 kcal")
    }
}

// Box - Overlapping content
@Composable
fun ImageWithBadge() {
    Box(contentAlignment = Alignment.Center) {
        Image(/* meal photo */)
        Badge(modifier = Modifier.align(Alignment.TopEnd)) {
            Text("New")
        }
    }
}

Lists with LazyColumn

@Composable
fun MealList(meals: List<Meal>) {
    LazyColumn(
        modifier = Modifier.fillMaxSize(),
        contentPadding = PaddingValues(16.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        items(
            items = meals,
            key = { meal -> meal.id }  // Stable keys for performance
        ) { meal ->
            MealCard(meal = meal)
        }
    }
}

Common Mistake

Always provide stable keys to list items. Using index as key causes issues with reordering and animations.

Material Design 3 Integration

@Composable
fun AppTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    dynamicColor: Boolean = true,
    content: @Composable () -> Unit
) {
    val colorScheme = when {
        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
            val context = LocalContext.current
            if (darkTheme) dynamicDarkColorScheme(context)
            else dynamicLightColorScheme(context)
        }
        darkTheme -> darkColorScheme()
        else -> lightColorScheme()
    }

    MaterialTheme(
        colorScheme = colorScheme,
        typography = Typography,
        content = content
    )
}

Interview Questions

Q: What is recomposition and how does Compose optimize it?

Recomposition is Compose's process of re-executing composable functions when state changes. Compose optimizes through:

  1. Skip unchanged composables - If inputs haven't changed, skip recomposition
  2. Positional memoization - Compose tracks composables by position in the tree
  3. Smart recomposition - Only affected parts recompose, not the entire tree
@Composable
fun ParentComposable() {
    var count by remember { mutableStateOf(0) }

    Column {
        Button(onClick = { count++ }) {
            Text("Count: $count")  // Recomposes when count changes
        }
        StaticChild()  // Does NOT recompose - no dependency on count
    }
}

Q: Explain the difference between remember and rememberSaveable.

  • remember - Survives recomposition, but NOT configuration changes (rotation)
  • rememberSaveable - Survives both recomposition AND configuration changes
var count by remember { mutableStateOf(0) }           // Lost on rotation
var count by rememberSaveable { mutableStateOf(0) }   // Preserved on rotation

Q: How do you create a custom Composable that accepts standard modifiers?

Accept Modifier as parameter with default value, apply it first:

@Composable
fun MyCustomCard(
    title: String,
    modifier: Modifier = Modifier  // Default empty modifier
) {
    Card(
        modifier = modifier          // Apply caller's modifier
            .fillMaxWidth()          // Then component's own modifiers
    ) {
        Text(title, modifier = Modifier.padding(16.dp))
    }
}

Common Mistakes

1. Forgetting Keys in Lists

// BAD - No keys, causes issues with reordering/deletion
LazyColumn {
    items(meals) { meal ->
        MealCard(meal)
    }
}

// GOOD - Stable keys
LazyColumn {
    items(meals, key = { it.id }) { meal ->
        MealCard(meal)
    }
}

2. Heavy Computation in Composable

// BAD - Runs on every recomposition
@Composable
fun ExpensiveComposable(items: List<Item>) {
    val sorted = items.sortedBy { it.date }  // Expensive!
    LazyColumn {
        items(sorted) { ... }
    }
}

// GOOD - Remember the computation
@Composable
fun OptimizedComposable(items: List<Item>) {
    val sorted = remember(items) {
        items.sortedBy { it.date }
    }
    LazyColumn {
        items(sorted) { ... }
    }
}

3. Not Using weight() in Row/Column

// BAD - Fixed sizes may not fill space
Row {
    Text("Label")
    Spacer(Modifier.width(8.dp))
    Text("Value")  // Won't push to end
}

// GOOD - weight distributes space
Row {
    Text("Label")
    Spacer(Modifier.weight(1f))  // Takes remaining space
    Text("Value")  // Pushed to end
}

Summary Table

ConceptPurposeKey Points
@ComposableDeclare UI functionsCannot return values other than Unit
ModifierConfigure appearance/behaviorOrder matters (chain wrapping)
rememberCache across recompositionLost on config change
rememberSaveableCache across config changesUse for user input
LazyColumnEfficient scrolling listsAlways provide stable keys
MaterialThemeConsistent themingDynamic colors on Android 12+

Next in series: Part 2 - State Management covers ViewModel, StateFlow, and reactive patterns.

← Previous

Android State Management Deep Dive

Next →

Event Sourcing Series Part 5: Key Concepts and Patterns