Jetpack Compose


1. Recomposition

Q: What is recomposition in Jetpack Compose?

Recomposition is when Compose re-executes composable functions to update the UI after state changes. Compose intelligently recomposes only the composables that read the changed state, not the entire tree.

Q: What triggers recomposition?

Any state read by a composable changing its value. This includes mutableStateOf, StateFlow.collectAsState(), or any State<T> object that the composable reads during execution.

Q: What are the key rules composables must follow?

  1. Idempotent: Same inputs produce same UI
  2. Side-effect free: No external mutations in the function body
  3. Independent of order: Don't assume execution order
  4. Can be skipped: Compose may skip recomposition if inputs haven't changed

Q: How does Compose decide what to skip?

Compose compares parameters using equals(). If all parameters are equal to the previous composition, it skips recomposition. For this to work, parameters must be stable—either primitives, immutable data classes, or annotated with @Stable/@Immutable.


2. The Three Phases of Compose

To render a frame, Compose goes through three distinct phases. Understanding this is key to performance.

  1. Composition: "What to show". Runs composable functions, builds the UI tree.
  2. Layout: "Where to place it". Measures and arranges children.
  3. Drawing: "How to render it". Draws to the canvas.

Performance Tip: You can skip phases!

  • If you only change a color, Compose skips Composition and Layout, running only Drawing.
  • If you use a lambda modifier (e.g., Modifier.offset { ... } instead of Modifier.offset(...)), you read the state during Layout, skipping Composition.
    • Rule: Read state effectively in the lowest possible phase.

3. Modifiers: Order & Scope

Q: Does modifier order matter? YES. Modifiers wrap the element in layers, executed from outer to inner (left to right).

Box(
    Modifier
        .size(50.dp)       // Outer layer: sets size
        .background(Red)   // Middle layer: paints red background
        .padding(10.dp)    // Inner layer: pads content
        .background(Blue)  // Innermost: paints blue on the padded area
)
  • clickable makes a difference depending on where it is relative to padding.

Q: What are scoped modifiers? Some modifiers are only available inside specific layouts because they rely on that parent's data.

  • align(Alignment.Center) is only in BoxScope.
  • weight(1f) is only in RowScope or ColumnScope.
  • Why? The parent defines the Scope interface, and these modifiers are extension functions on that scope.

4. Stability and Performance

Q: What makes a type "stable" in Compose?

A stable type guarantees that:

  1. equals() always returns the same result for the same instances
  2. If a public property changes, Compose is notified
  3. All public properties are also stable

Primitives, String, and immutable data classes with stable properties are stable by default.

Q: What makes a type "unstable"?

  • Mutable collections (MutableList, ArrayList)
  • Data classes with mutable properties
  • Classes from external libraries Compose can't verify
  • Lambdas captured in composition

Q: How do you fix stability issues?

Problem Solution
Mutable collection Use immutable List, Set, Map
External library class Wrap in stable wrapper or use @Immutable
Lambda recreation Hoist lambda or use remember
Unknown class Annotate with @Stable if you guarantee stability

Q: How do you debug recomposition issues?

  • Use Layout Inspector's "Show Recomposition Counts"
  • Add SideEffect { log("Recomposed") } temporarily
  • Check stability with Compose Compiler reports
  • Profile with Android Studio Profiler

5. State Management

Q: Explain the different ways to hold state in Compose.

API Survives Recomposition Survives Config Change Survives Process Death
remember
rememberSaveable
ViewModel ❌ (use SavedStateHandle)

Q: What is state hoisting?

Moving state up from a composable to its caller, making the composable stateless. The composable receives the state value and a callback to request changes. Benefits:

  • Reusable composables
  • Easier testing
  • Single source of truth
  • Parent controls the state

Q: When should state live in ViewModel vs Composable?

ViewModel Composable (remember)
Business logic state Pure UI state
Data from repository Animation state
Survives config change Scroll position, expansion state
Shared across composables Local to one composable

6. Side Effects

Q: What are side effects and why do they need special handling?

Side effects are operations that escape the composable's scope—network calls, database writes, navigation, analytics. Composables can recompose at any time, in any order, so side effects in the body would execute unpredictably.

Q: Explain the side effect APIs.

API Purpose Lifecycle
LaunchedEffect(key) Launch coroutine Cancelled when key changes or leaves composition
DisposableEffect(key) Setup/cleanup resources Cleanup called when key changes or leaves composition
SideEffect Non-suspending effect. Runs after every successful recomposition.
rememberCoroutineScope() Get scope for event handlers Tied to composition lifecycle
derivedStateOf Compute value from other states, avoiding recomposition Updates only when result changes
produceState Convert non-Compose state to Compose state Runs coroutine to produce values

Q: LaunchedEffect(Unit) vs LaunchedEffect(true) vs rememberCoroutineScope?

  • LaunchedEffect(Unit): Runs once when entering composition. (The key never changes).
  • LaunchedEffect(key): Runs on enter, and restarts whenever key changes.
  • rememberCoroutineScope: Does not launch automatically. Gives you a scope to launch manually (e.g., in onClick).

Q: When do you use DisposableEffect?

For resources that need explicit cleanup:

  • Registering/unregistering listeners
  • Binding to lifecycle
  • Managing third-party SDK lifecycles
  • Callback registrations

7. Composition Local

Q: What is CompositionLocal?

A way to pass data implicitly through the composition tree without explicit parameters. Useful for cross-cutting concerns like theme, navigation, or locale.

Q: compositionLocalOf vs staticCompositionLocalOf?

Type Recomposition Use Case
compositionLocalOf Only readers recompose when value changes Frequently changing values
staticCompositionLocalOf Entire subtree recomposes when value changes Rarely changing values (theme, locale)

Q: When should you use CompositionLocal?

Sparingly. Good uses:

  • Theme/colors (already provided by MaterialTheme)
  • Navigation controller
  • Dependency injection in Compose

Avoid for business data—use parameters for explicit, traceable data flow.


8. Lists and Performance

Q: How do you build efficient lists in Compose?

Use LazyColumn/LazyRow instead of Column/Row with forEach. Lazy composables only compose visible items.

Q: What is the key parameter and why is it important?

Keys help Compose identify which items changed, moved, or stayed the same. Without keys, Compose uses position—moving an item causes unnecessary recomposition of all items between old and new positions. With stable keys (like IDs), Compose preserves state and animates correctly.

Q: How do you optimize LazyColumn performance?

  1. Always provide keys: Use unique, stable identifiers
  2. Avoid heavy computation in items: Use remember or move to ViewModel
  3. Use contentType: Helps Compose reuse compositions
  4. Avoid nesting scrollable containers: Causes measurement issues
  5. Use derivedStateOf for scroll-dependent state: Reduces recomposition

9. Advanced Layouts

Q: How does the Layout composable work? It allows you to manually measure and place children.

  1. Measure: Ask children how big they want to be (measurable.measure(constraints)).
  2. Decide Size: Determine your own size based on children.
  3. Place: Position children relative to your generic coordinate (placeable.place(x, y)).

Q: What involves SubcomposeLayout? Standard Layouts measure everything in one pass. SubcomposeLayout allows you to measure some children, and use that size to decide what else to compose (e.g., BoxWithConstraints, LazyColumn). It's more powerful but slower.

Q: Intrinsic Measurements "How tall would you be if you had infinite width?" vs "How tall are you actually?" Used when you need children to match heights before measuring (e.g., two text fields in a row matching height).


10. Semantics & Accessibility

Q: What is the Semantics Tree? A parallel tree to the Composition Tree, used by:

  1. Accessibility Services: TalkBack, Switch Access.
  2. Testing Framework: composeTestRule.onNodeWithText(...).

Q: How do you improve Accessibility?

  • contentDescription: Necessary for Images/Icons.
  • Modifier.semantics { ... }: Add metadata manually.
    • onClick: Label what the click does.
    • stateDescription: "Checked", "Volume 50%".
    • role: "Button", "Checkbox".
  • Touch Target Size: Material components enforce 48dp.

11. Navigation

Q: How does Navigation work in Compose?

Navigation Compose uses a NavHost composable with a NavController. Routes are strings (optionally with arguments). Each route maps to a composable destination.

Q: How do you pass arguments between screens?

  • Define route with placeholders: "user/{userId}"
  • Navigate with values: navController.navigate("user/123")
  • Receive in destination via NavBackStackEntry.arguments

For complex objects, pass IDs and fetch data in the destination's ViewModel.

Q: How do you handle navigation events from ViewModel?

Expose a one-time event (Channel or SharedFlow) that the UI collects and acts upon. Don't hold NavController in ViewModel—it's a UI concern.


12. Interop with Views

Q: How do you use Views inside Compose?

Use AndroidView composable. Provide a factory to create the View and an update lambda for when state changes. The View is created once and updated on recomposition.

Q: How do you use Compose inside Views?

Use ComposeView in XML or create it programmatically. Call setContent {} to provide composables. In Fragments, set viewCompositionStrategy to match the Fragment's lifecycle.

Q: What's important when mixing Compose and Views?

  • Theme bridging: Use MdcTheme or custom bridging
  • Lifecycle: Ensure Compose respects the View's lifecycle owner
  • State: Be careful about state ownership—one source of truth
  • Performance: Minimize interop boundaries for best performance

Quick Reference

Topic Key Points
Recomposition Triggers on state change; skips stable unchanged inputs; keep composables pure
Phases Composition (What) -> Layout (Where) -> Drawing (How). Skip phases for perf.
Modifiers Order matters (Outer -> Inner); Scoped modifiers (align, weight)
Stability Immutable = stable; mutable collections = unstable; use @Stable/@Immutable when needed
State remember (composition), rememberSaveable (config change), ViewModel (business logic)
Side Effects LaunchedEffect (Key changes); DisposableEffect (Cleanup); SideEffect (Post-composition)
Lists Use LazyColumn; always provide keys; use contentType for mixed lists
Layouts Layout for custom measurement; SubcomposeLayout for dependency; Intrinsics
Semantics Accessibility tree; contentDescription; Modifier.semantics for testing/a11y
Navigation NavHost + NavController; string routes; pass IDs not objects

results matching ""

    No results matching ""