> For the complete documentation index, see [llms.txt](https://notes.tejpratapsingh.com/_/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://notes.tejpratapsingh.com/_/android-tips/interview/compose/compose-part-1.md).

# Compose Part 1

#### **Q1. Explain the difference between `remember` and `rememberSaveable` and when each is appropriate.**

`remember` stores a value in the Composition and survives recomposition, but it is destroyed when the Activity or process is killed (e.g., during a configuration change or process death). Use it for ephemeral UI state like scroll position, animation progress, or temporary form input that does not need to persist across the app's lifecycle. `rememberSaveable`, on the other hand, uses the `SavedStateRegistry` to automatically serialize the value into the `Bundle` provided by the system. This means it survives both configuration changes (like screen rotation) and process death/restoration. However, `rememberSaveable` only works with types that can be stored in a `Bundle` (primitives, `Parcelable`, `Serializable`, `List`, `Map`, etc.), or types for which you provide a custom `Saver`. For complex objects like a `ViewModel` or a network response DTO that is not `Parcelable`, you must either write a custom `Saver` or simply use `remember` and accept that the state will be lost.

```kotlin
@Composable
fun LoginForm() {
    // Lost on rotation / process death
    var username by remember { mutableStateOf("") }

    // Survives rotation and process death
    var password by rememberSaveable { mutableStateOf("") }

    // Custom Saver for a non-Bundle type
    data class User(val name: String, val age: Int)
    val userSaver = Saver<User, List<<Any>>(
        save = { listOf(it.name, it.age) },
        restore = { User(it[0] as String, it[1] as Int) }
    )
    var user by rememberSaveable(stateSaver = userSaver) { mutableStateOf(User("Alex", 25)) }

    OutlinedTextField(value = username, onValueChange = { username = it }, label = { Text("Username") })
    OutlinedTextField(value = password, onValueChange = { password = it }, label = { Text("Password") })
}
```

***

#### **Q2. What triggers recomposition in Compose, and how does the compiler optimize it via stability inference?**

Recomposition is triggered whenever a `@Composable` function reads a `State` object (e.g., `mutableStateOf`) whose value has changed. Compose tracks these reads during the composition phase using an observation system linked to the snapshot state. When the state value mutates, Compose invalidates all composable scopes that read that state and schedules them for recomposition. The Compose Compiler plugin optimizes this by analyzing parameter stability at compile time. If all parameters of a composable are deemed **stable** (immutable primitives, `@Immutable`/`@Stable` types), the compiler marks the function as **skippable**, meaning it can skip recomposition if the arguments are equal (`==`). The compiler infers stability automatically for Kotlin data classes with only stable properties, but it treats interfaces, `var` properties, and standard collection types (`ArrayList`, `HashMap`, etc.) as unstable by default, which disables skipping.

```kotlin
// The compiler sees 'name' as stable (String) and marks this as skippable.
@Composable
fun Greeting(name: String) {
    Text(text = "Hello, $name")
}

// The compiler sees 'users' as unstable (ArrayList) and marks this as non-skippable.
@Composable
fun UserList(users: ArrayList<String>) {
    Column {
        users.forEach { Text(it) }
    }
}

// Fix: Use List<String> (immutable interface) or wrap in @Immutable.
@Immutable
data class UserListData(val users: List<String>)

@Composable
fun OptimizedUserList(data: UserListData) {
    Column {
        data.users.forEach { Text(it) }
    }
}
```

***

#### **Q3. How do `@Stable`, `@Immutable`, and `@NonRestartableComposable` annotations affect recomposition behavior?**

`@Immutable` tells the Compose compiler that the annotated class and all its fields are deeply immutable. Once constructed, nothing inside it can change. This allows Compose to treat any instance as stable and skip recompositions if the instance reference (`===`) is the same. `@Stable` is a weaker contract: it promises that if any property changes, Compose will be notified (typically because those properties are backed by `mutableStateOf`). This is used for classes like `ViewModel` or controller classes where the object identity stays the same but internal state changes. `@NonRestartableComposable` prevents the compiler from generating "restart" logic for that function. Normally, the compiler wraps composables so that if a state read inside them changes, only that specific scope recomposes (restartable). With this annotation, the function becomes a transparent call site: if it reads state, the *caller* recomposes instead. This is useful for tiny inline helper composables or `Layout` blocks where you want to avoid unnecessary composition overhead.

```kotlin
@Immutable
data class Profile(val name: String, val avatarUrl: String)

@Stable
class CounterController {
    var count by mutableIntStateOf(0)
        private set
    fun increment() = count++
}

// Without @NonRestartableComposable, this wrapper gets its own restart scope.
// With it, recomposition bubbles up to the caller, reducing composition overhead.
@NonRestartableComposable
@Composable
fun InlinedText(text: String, modifier: Modifier = Modifier) {
    Text(text = text, modifier = modifier)
}

@Composable
fun Demo() {
    val controller = remember { CounterController() }
    val profile = remember { Profile("Sam", "url") }

    Column {
        // Skips if profile reference hasn't changed, because @Immutable guarantees equality.
        Text("Name: ${profile.name}")

        // Does NOT skip, because @Stable allows internal mutations that Compose observes.
        Button(onClick = controller::increment) {
            Text("Count: ${controller.count}")
        }
    }
}
```

***

#### **Q4. Why is it problematic to pass unstable types (e.g., `ArrayList`, `ViewModel`) directly into Composable parameters?**

The Compose compiler treats types as unstable if it cannot prove they are immutable. Standard Java collections (`ArrayList`, `HashMap`, `Calendar`, etc.) and open classes/interfaces are unstable by default. When an unstable type is passed as a parameter, the compiler marks the composable as **non-skippable**, meaning it will recompose every time the parent recomposes—even if the data inside the collection hasn't actually changed. This destroys the primary performance benefit of Compose: fine-grained skipping. A `ViewModel` is technically marked `@Stable` in many architectures, but passing it as a parameter still forces recomposition if the parent recomposes, because the compiler cannot guarantee that the ViewModel's internal state changes are properly observed at the parameter level. The solution is to extract the exact stable data you need (e.g., `List<String>` instead of `ArrayList<String>`, or individual `State` values) and pass those down, or wrap the data in an `@Immutable` data class.

```kotlin
// BAD: ArrayList is unstable. UserList will recompose on every parent recomposition.
@Composable
fun BadUserList(users: ArrayList<String>) {
    Column { users.forEach { Text(it) } }
}

// BAD: Passing the entire ViewModel makes the composable tightly coupled and harder to skip.
@Composable
fun BadProfileCard(vm: ProfileViewModel) {
    Text(vm.userName)
}

// GOOD: Pass only the stable data needed.
@Composable
fun GoodUserList(users: List<String>) {
    Column { users.forEach { Text(it) } }
}

// GOOD: Hoist the state read to the call site and pass the primitive.
@Composable
fun GoodProfileCard(userName: String, onEdit: () -> Unit) {
    Text(userName)
    Button(onClick = onEdit) { Text("Edit") }
}

// Usage:
@Composable
fun Screen(vm: ProfileViewModel) {
    val name by vm.userName.collectAsStateWithLifecycle()
    GoodProfileCard(userName = name, onEdit = vm::onEditClicked)
}
```

***

#### **Q5. What is the role of `SnapshotState` and how does it integrate with Compose's observation system?**

`SnapshotState` is the underlying mutable state system that powers Compose (and potentially other UI frameworks). When you create a state using `mutableStateOf()`, you are creating a `SnapshotState` object. The "Snapshot" part refers to the fact that all state changes in Compose happen within an atomic snapshot. When a value is changed (e.g., `state.value = 5`), the change is not applied globally immediately; it is recorded in the current snapshot. When the snapshot is applied (typically at the end of the event loop), all observers that read that state during composition are notified. Compose hooks into this notification system: during composition, it records which `State` objects each composable reads. When a snapshot is applied and those states change, Compose invalidates the associated composable scopes and schedules them for recomposition. This is why state must be read *inside* the composable body (or in lambdas that execute during composition) to be observed. Reading it in a side effect or a callback does not establish an observation link.

```kotlin
@Composable
fun SnapshotDemo() {
    val counter = remember { mutableIntStateOf(0) }
    val derived = remember { derivedStateOf { counter.intValue * 2 } }

    // This read is observed: Text will recompose when counter changes.
    Text("Counter: ${counter.intValue}")

    // This read is also observed via derivedStateOf, but only recomposes if the doubled value changes.
    Text("Doubled: ${derived.value}")

    // SideEffect runs after composition, but does NOT observe state for recomposition.
    // It only sees the value at the time it ran.
    SideEffect {
        Log.d("Compose", "Current value: ${counter.intValue}")
    }

    Button(onClick = { counter.intValue++ }) {
        Text("Increment")
    }
}
```

***

#### **Q6. Explain the difference between `derivedStateOf` and `remember(key)` — when would you choose one over the other?**

`remember(key)` caches a value and recomputes it whenever the `key` changes (using structural equality `==`). It is ideal when you have a direct input that changes infrequently and you want to avoid recalculating an expensive transformation. `derivedStateOf`, however, creates a `State` object that observes other `State` objects. It only notifies its observers when the *result* of the calculation changes, not when the inputs change. This is crucial for preventing unnecessary recompositions when the calculation filters or reduces data. For example, if you have a list of 1000 items and you want to know if any item is selected, `remember(selectedIds)` would recompute every time the set reference changes (which might be every time an item is clicked). `derivedStateOf { selectedIds.isNotEmpty() }` will only trigger a recomposition of readers when the boolean result flips from `false` to `true` or vice versa, even if the set reference changes multiple times in between. Use `remember(key)` for external non-state inputs or one-off expensive calculations; use `derivedStateOf` when deriving a new observable state from existing observable state to minimize downstream recompositions.

```kotlin
@Composable
fun SearchScreen(items: List<Item>, query: String) {
    // remember(key): Recomputes the filtered list every time 'query' changes.
    // This is appropriate because 'query' is an external string parameter.
    val filteredByQuery = remember(query) {
        items.filter { it.name.contains(query, ignoreCase = true) }
    }

    // derivedStateOf: Only notifies observers when the boolean result changes.
    // Useful if multiple items are selected/deselected rapidly but UI only cares about "is any selected".
    val selectedIds = remember { mutableStateOf(setOf<String>()) }
    val hasSelection by remember { derivedStateOf { selectedIds.value.isNotEmpty() } }

    // This entire block only recomposes when hasSelection flips, not on every id add/remove.
    AnimatedVisibility(visible = hasSelection) {
        FloatingActionButton(onClick = { /* delete selected */ }) {
            Icon(Icons.Default.Delete, contentDescription = "Delete")
        }
    }

    LazyColumn {
        items(filteredByQuery, key = { it.id }) { item ->
            // ...
        }
    }
}
```

***

#### **Q7. What happens if you write `var text by remember { mutableStateOf("") }` inside a Composable versus hoisting it to a ViewModel?**

When you declare `var text by remember { mutableStateOf("") }` directly inside a `@Composable` function, the state is local to that composable's slot in the Composition tree. If that composable leaves the Composition (e.g., due to a conditional `if` block or navigation), the state is destroyed. It also does not survive configuration changes like screen rotation. Furthermore, sibling or child composables cannot easily access this state without it being passed as parameters through multiple layers (prop drilling). Hoisting the same state to a `ViewModel` (or any class scoped to the `ViewModelStoreOwner`) solves these problems. The `ViewModel` survives configuration changes and process death (when combined with `SavedStateHandle`). It centralizes business logic, makes the state accessible to multiple composables via `collectAsStateWithLifecycle()`, and enforces a unidirectional data flow where the UI is a pure function of the ViewModel's state. The downside is slightly more boilerplate and the risk of retaining state longer than necessary if the ViewModel outlives the specific screen.

```kotlin
// LOCAL STATE: Lost on rotation, lost when composable leaves composition.
@Composable
fun LocalLogin() {
    var text by remember { mutableStateOf("") }
    OutlinedTextField(value = text, onValueChange = { text = it })
}

// HOISTED STATE: Survives rotation, accessible from multiple screens, testable.
class LoginViewModel : ViewModel() {
    private val _uiState = MutableStateFlow(LoginUiState())
    val uiState: StateFlow<<LoginUiState> = _uiState.asStateFlow()

    fun onEmailChange(email: String) {
        _uiState.update { it.copy(email = email, isValid = email.contains("@")) }
    }
}

@Composable
fun HoistedLogin(vm: LoginViewModel = viewModel()) {
    val state by vm.uiState.collectAsStateWithLifecycle()

    Column {
        OutlinedTextField(
            value = state.email,
            onValueChange = vm::onEmailChange,
            isError = !state.isValid
        )
        if (!state.isValid) {
            Text("Invalid email", color = MaterialTheme.colorScheme.error)
        }
    }
}
```

***

#### **Q8. How do you implement unidirectional data flow in a Compose-heavy application?**

Unidirectional Data Flow (UDF) means that state flows in one direction—from a state holder (like a `ViewModel`) down to the UI—and events flow in the opposite direction—from the UI up to the state holder. The UI should never mutate state directly; instead, it should invoke callbacks or fire events (often called "intents" or "actions") that the state holder processes. This makes the UI a pure function of state, which makes it predictable, easy to test, and robust against bugs. In Compose, the UI layer consists of stateless composables that receive state and lambda callbacks as parameters. The state holder (e.g., a `ViewModel` exposing a `StateFlow<<UiState>`) is the single source of truth. When a user interacts with the UI (e.g., clicks a button or types in a field), the composable calls the provided lambda, which typically delegates to the `ViewModel`. The `ViewModel` updates its internal state, which emits a new `UiState` value, triggering a recomposition of the UI to reflect the change.

```kotlin
// 1. Define a single UiState data class representing the entire screen.
data class SearchUiState(
    val query: String = "",
    val results: List<<Movie> = emptyList(),
    val isLoading: Boolean = false,
    val error: String? = null
)

// 2. ViewModel is the single source of truth.
class SearchViewModel(private val repo: MovieRepository) : ViewModel() {
    private val _state = MutableStateFlow(SearchUiState())
    val state: StateFlow<<SearchUiState> = _state.asStateFlow()

    fun onQueryChange(query: String) {
        _state.update { it.copy(query = query) }
        if (query.length > 2) search(query)
    }

    private fun search(q: String) {
        viewModelScope.launch {
            _state.update { it.copy(isLoading = true, error = null) }
            try {
                val movies = repo.search(q)
                _state.update { it.copy(results = movies, isLoading = false) }
            } catch (e: Exception) {
                _state.update { it.copy(error = e.message, isLoading = false) }
            }
        }
    }
}

// 3. Stateless UI: pure function of state + events.
@Composable
fun SearchScreen(
    uiState: SearchUiState,
    onQueryChange: (String) -> Unit,
    onMovieClick: (Movie) -> Unit,
    modifier: Modifier = Modifier
) {
    Column(modifier = modifier.padding(16.dp)) {
        OutlinedTextField(
            value = uiState.query,
            onValueChange = onQueryChange,
            label = { Text("Search movies") },
            modifier = Modifier.fillMaxWidth()
        )
        if (uiState.isLoading) CircularProgressIndicator(Modifier.align(Alignment.CenterHorizontally))
        uiState.error?.let { Text(it, color = Color.Red) }
        LazyColumn {
            items(uiState.results, key = { it.id }) { movie ->
                ListItem(
                    headlineContent = { Text(movie.title) },
                    modifier = Modifier.clickable { onMovieClick(movie) }
                )
            }
        }
    }
}

// 4. Stateful wrapper connects ViewModel to UI.
@Composable
fun SearchScreenRoute(vm: SearchViewModel = viewModel()) {
    val state by vm.state.collectAsStateWithLifecycle()
    SearchScreen(
        uiState = state,
        onQueryChange = vm::onQueryChange,
        onMovieClick = { movie -> /* navigate to detail */ }
    )
}
```

***

#### **Q9. What are the trade-offs between `ViewModel` state holders versus plain class state holders in Compose?**

A `ViewModel` is lifecycle-aware and survives configuration changes because it is retained by the `ViewModelStore` scoped to a `NavBackStackEntry`, `Activity`, or `Fragment`. This makes it ideal for business logic, network calls, and state that must persist across rotation. However, it introduces a dependency on the Android Architecture Components and is often overkill for purely UI logic (like animation state or scroll position). A plain class state holder (sometimes called a `UiStateHolder` or simply hoisted into `remember`) is lighter, easier to instantiate in previews, and easier to unit test because it does not require a `ViewModel` testing harness. The downside is that it is destroyed when the composable leaves the Composition or when the host `Activity` is recreated. For complex screens, a hybrid approach works best: use a `ViewModel` for domain state and a plain class (or `remember`ed instance) for UI-specific state. Jetpack Compose documentation recommends this pattern as "state holders."

```kotlin
// Plain class: lightweight, easy to preview, lost on rotation.
class FormStateHolder {
    var email by mutableStateOf("")
    var password by mutableStateOf("")
    val isValid by derivedStateOf { email.isNotEmpty() && password.length >= 6 }
}

@Composable
fun LoginForm() {
    val form = remember { FormStateHolder() }
    Column {
        OutlinedTextField(value = form.email, onValueChange = { form.email = it })
        OutlinedTextField(value = form.password, onValueChange = { form.password = it })
        Button(onClick = { /* submit */ }, enabled = form.isValid) { Text("Login") }
    }
}

// ViewModel: survives rotation, handles async work, heavier.
class LoginViewModel(
    private val authRepo: AuthRepository
) : ViewModel() {
    private val _state = MutableStateFlow(LoginUiState())
    val state = _state.asStateFlow()

    fun login() {
        viewModelScope.launch {
            _state.update { it.copy(isLoading = true) }
            val result = authRepo.login(_state.value.email, _state.value.password)
            _state.update { it.copy(isLoading = false, isSuccess = result.isSuccess) }
        }
    }
}
```

***

#### **Q10. How do you handle configuration changes (e.g., rotation) with Compose state without relying on `ViewModel`?**

If you want to avoid `ViewModel` (for example, in a lightweight feature module or a reusable component library), you can use `rememberSaveable` to persist primitive and simple state across configuration changes. For more complex objects, you provide a custom `Saver` that tells the system how to serialize and deserialize the object into a `Bundle`. Another approach is to use `rememberRetained` from libraries like Circuit or Decompose, which mimics `ViewModel`'s retention semantics without the AAC dependency. However, `rememberSaveable` does not survive process death for complex objects unless a `Saver` is provided, and it is not suitable for large data sets or objects holding references to Contexts, CoroutineScopes, or Repositories. For UI state that is cheap to recompute (like a toggle or text input), `rememberSaveable` is sufficient. For business state that requires async initialization, a `ViewModel` or a retained plain class is still the better choice.

```kotlin
// Using rememberSaveable with a custom Saver for a data class.
data class EditorState(val title: String, val body: String, val lastModified: Long)

val EditorStateSaver = Saver<<EditorState, Bundle>(
    save = { state ->
        Bundle().apply {
            putString("title", state.title)
            putString("body", state.body)
            putLong("lastModified", state.lastModified)
        }
    },
    restore = { bundle ->
        EditorState(
            title = bundle.getString("title") ?: "",
            body = bundle.getString("body") ?: "",
            lastModified = bundle.getLong("lastModified")
        )
    }
)

@Composable
fun NoteEditor() {
    var state by rememberSaveable(stateSaver = EditorStateSaver) {
        mutableStateOf(EditorState("", "", System.currentTimeMillis()))
    }

    Column(Modifier.fillMaxSize().padding(16.dp)) {
        OutlinedTextField(
            value = state.title,
            onValueChange = { state = state.copy(title = it, lastModified = System.currentTimeMillis()) },
            label = { Text("Title") }
        )
        OutlinedTextField(
            value = state.body,
            onValueChange = { state = state.copy(body = it, lastModified = System.currentTimeMillis()) },
            label = { Text("Body") },
            modifier = Modifier.weight(1f)
        )
        Text("Last saved: ${Date(state.lastModified)}")
    }
}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://notes.tejpratapsingh.com/_/android-tips/interview/compose/compose-part-1.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
