> 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-2.md).

# Compose Part 2

#### **Q11. Describe the purpose and limitations of `CompositionLocal` — when is it appropriate to use versus avoid?**

`CompositionLocal` provides a way to pass data implicitly through the Composition tree without manually threading it through every composable parameter. It is the mechanism behind `LocalContext`, `LocalDensity`, `LocalContentColor`, and `MaterialTheme`. The primary purpose is to provide ambient values that are globally relevant to a subtree, such as theme colors, typography, locale settings, or scroll state. However, the major limitation is that it creates hidden dependencies: a composable that reads a `CompositionLocal` will behave differently depending on where it is placed in the tree, making the code harder to reason about, test, and preview. If overused for business logic or feature-specific state, it leads to "spaghetti" state management where data flow is opaque. Additionally, non-static `CompositionLocal` values (those that change frequently) can cause large subtrees to recompose because any read establishes an observation. You should use `CompositionLocal` for true cross-cutting concerns like theming, insets, or accessibility settings, but avoid it for screen-specific state, ViewModels, or navigation controllers.

```kotlin
// Appropriate: Providing theme-related values or scroll state that many descendants need.
val LocalCustomColors = staticCompositionLocalOf<<CustomColors> { error("No colors provided") }

@Composable
fun AppTheme(content: @Composable () -> Unit) {
    val colors = remember { CustomColors(primary = Color.Blue) }
    CompositionLocalProvider(LocalCustomColors provides colors) {
        MaterialTheme(content = content)
    }
}

@Composable
fun ThemedButton(text: String, onClick: () -> Unit) {
    val colors = LocalCustomColors.current // Clean, expected usage for theme tokens.
    Button(onClick = onClick, colors = ButtonDefaults.buttonColors(containerColor = colors.primary)) {
        Text(text)
    }
}

// Avoid: Using CompositionLocal for business logic or ViewModels.
// val LocalUserViewModel = compositionLocalOf<UserViewModel> { error("Missing") }
// BAD: Hidden dependency makes testing and previewing impossible.
```

***

#### **Q12. How would you share state between sibling Composables without passing callbacks through multiple layers?**

When two sibling composables need to share the same state, the canonical solution is to hoist that state to their closest common ancestor (a pattern known as "state hoisting"). If the siblings are in the same screen, this could be a parent composable or a shared `ViewModel`. If the siblings are in different destinations within a `NavHost`, the best approach is to scope a `ViewModel` to the parent `NavBackStackEntry` so that both destinations access the same instance via `viewModel(viewModelStoreOwner = parentEntry)`. This avoids "prop drilling," where callbacks and state are passed through many intermediate layers that do not use them. Another valid but less common approach is using a shared plain class state holder instantiated with `remember` at the common ancestor. The key principle is that the shared state should live at the lowest common level that encompasses all consumers, ensuring that the UI layer remains a pure function of that single source of truth.

```kotlin
// Parent screen hoists state and shares it with two sibling panels.
class SharedViewModel : ViewModel() {
    private val _items = MutableStateFlow(listOf("A", "B", "C"))
    val items: StateFlow<List<String>> = _items.asStateFlow()
    fun remove(item: String) { _items.update { it - item } }
}

@Composable
fun SplitScreen(vm: SharedViewModel = viewModel()) {
    val items by vm.items.collectAsStateWithLifecycle()
    Row(Modifier.fillMaxSize()) {
        // Sibling 1: Displays list
        ItemList(
            items = items,
            modifier = Modifier.weight(1f)
        )
        // Sibling 2: Shows controls that mutate the same list
        ControlPanel(
            count = items.size,
            onClear = { vm.remove(items.firstOrNull() ?: return@ControlPanel) },
            modifier = Modifier.weight(1f)
        )
    }
}

@Composable
fun ItemList(items: List<String>, modifier: Modifier = Modifier) {
    LazyColumn(modifier = modifier) {
        items(items, key = { it }) { Text(it) }
    }
}

@Composable
fun ControlPanel(count: Int, onClear: () -> Unit, modifier: Modifier = Modifier) {
    Column(modifier = modifier.padding(16.dp)) {
        Text("Total items: $count")
        Button(onClick = onClear) { Text("Remove First") }
    }
}
```

***

#### **Q13. What is the difference between `produceState`, `collectAsState`, and `collectAsStateWithLifecycle`?**

`produceState` is a bridge for converting non-Flow asynchronous work—such as a suspend function, callback-based API, or `LiveData`—into a Compose `State`. It launches a coroutine scoped to the Composition and allows you to manually set the state value inside a block, making it ideal for one-shot operations or complex async initialization. `collectAsState` is a simpler convenience for Kotlin `Flow` that collects the flow into a `State` object. Its critical downside is that it collects the flow continuously regardless of the UI lifecycle, meaning it will keep emitting and recomposing the UI even when the app is in the background, which can waste resources and trigger unnecessary work. `collectAsStateWithLifecycle` (from `androidx.lifecycle:lifecycle-runtime-compose`) solves this by respecting the lifecycle of the host `LifecycleOwner`. It only collects the flow when the lifecycle is at least `STARTED` (or another specified state), automatically pausing collection when the screen is not visible and resuming when it returns. For any UI-bound `StateFlow` or `Flow`, `collectAsStateWithLifecycle` is the preferred and safest option.

```kotlin
// produceState: Wrapping a suspend function or callback API.
@Composable
fun UserProfile(userId: String) {
    val user by produceState<<User?>(initialValue = null, userId) {
        value = userRepository.fetchUser(userId) // One-shot suspend call
    }
    Text(user?.name ?: "Loading...")
}

// collectAsState: Simple but lifecycle-unaware. Avoid in production UI.
@Composable
fun BadCounter(vm: CounterViewModel) {
    val count by vm.countFlow.collectAsState() // Collects even in background!
    Text("$count")
}

// collectAsStateWithLifecycle: Lifecycle-aware, recommended for all UI flows.
@Composable
fun GoodCounter(vm: CounterViewModel) {
    val count by vm.countFlow.collectAsStateWithLifecycle(
        lifecycleOwner = LocalLifecycleOwner.current,
        minActiveState = Lifecycle.State.STARTED // Default; pauses in background.
    )
    Text("$count")
}

// ViewModel side
class CounterViewModel : ViewModel() {
    private val _countFlow = MutableStateFlow(0)
    val countFlow: StateFlow<Int> = _countFlow.asStateFlow()
    fun increment() = _countFlow.update { it + 1 }
}
```

***

#### **Q14. How do you gracefully handle process death and restoration with Compose Navigation?**

Process death occurs when the system kills your app to reclaim memory, and the user later returns via the recent tasks list. Compose Navigation automatically saves and restores the back stack state using the framework's `SavedState` mechanism, but you must ensure that your destination arguments and ViewModel state are also restorable. For navigation arguments, use primitive types or `Parcelable`/`Serializable` in your route definitions so the `NavController` can persist them in the saved state `Bundle`. In your `ViewModel`, use `SavedStateHandle` to persist UI state that must survive process death; `SavedStateHandle` integrates directly with the `NavBackStackEntry`'s arguments and saved state. For complex objects, provide a custom `Saver` or use Kotlin Serialization with type-safe navigation (Compose Navigation 2.8+). Additionally, you can manually call `navController.saveState()` and restore it via `NavHost(rememberNavController(createdState))` if you are managing your own navigation state. The key is to never store non-serializable objects (like `Context`, `Repository` instances, or `CoroutineScope`) in anything that is saved to a `Bundle`.

```kotlin
// Type-safe navigation with primitives/serializable args (survives process death).
@Serializable
data class DetailRoute(val itemId: String, val scrollIndex: Int = 0)

@Composable
fun AppNavHost() {
    val navController = rememberNavController()
    NavHost(navController, startDestination = HomeRoute) {
        composable<<HomeRoute> { HomeScreen() }
        composable<<DetailRoute> { backStack ->
            val args: DetailRoute = backStack.toRoute()
            DetailScreen(itemId = args.itemId, initialScroll = args.scrollIndex)
        }
    }
}

// ViewModel using SavedStateHandle to survive process death.
class DetailViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
    // Restored automatically from NavBackStackEntry arguments or saved state.
    var scrollIndex by savedStateHandle.saveable { mutableIntStateOf(0) }
        private set

    fun onScroll(newIndex: Int) {
        scrollIndex = newIndex // Automatically persisted across process death.
    }
}

@Composable
fun DetailScreen(itemId: String, initialScroll: Int, vm: DetailViewModel = viewModel()) {
    val listState = rememberLazyListState(initialFirstVisibleItemIndex = initialScroll)
    // Sync scroll position back to ViewModel for saving.
    LaunchedEffect(listState.firstVisibleItemIndex) {
        vm.onScroll(listState.firstVisibleItemIndex)
    }
    LazyColumn(state = listState) { /* items */ }
}
```

***

#### **Q15. What is the "donut-hole skipping" optimization in Compose, and how do you ensure your code benefits from it?**

"Donut-hole skipping" (also called "group skipping" or simply "skipping") is the Compose compiler's ability to skip recomposing a composable and its entire subtree when all of its parameters are **stable** and have not changed according to equality (`==`). The name evokes the image of a parent recomposing while the "hole" (the child with stable inputs) remains untouched. To benefit from this optimization, you must ensure that every parameter passed to a composable is of a **stable** type. Primitives (`Int`, `Boolean`, `String`) and `@Immutable` data classes are stable. Standard collections like `ArrayList`, `HashMap`, or mutable classes are unstable by default and will disable skipping for that composable. You should replace them with Kotlin's immutable interfaces (`List`, `Map`) or annotate your data classes with `@Immutable`. Additionally, avoid capturing unstable variables in lambdas that are passed as parameters; instead, use `rememberUpdatedState` or pass stable callbacks. The Compose Compiler metrics report (`compose_compiler`) will flag which composables are "restartable but not skippable," helping you identify exactly where unstable parameters are hurting performance.

```kotlin
// BAD: ArrayList is unstable. ParentList will recompose even if items haven't changed.
@Composable
fun BadParentList(items: ArrayList<String>) {
    Column {
        items.forEach { BadChild(it) }
    }
}

// GOOD: List<String> is inferred as stable (immutable interface).
// The compiler marks this as skippable.
@Composable
fun GoodParentList(items: List<String>) {
    Column {
        items.forEach { GoodChild(it) }
    }
}

// GOOD: @Immutable data class ensures deep stability.
@Immutable
data class Product(val id: String, val name: String, val price: Double)

@Composable
fun ProductCard(product: Product, onClick: () -> Unit) {
    // If product and onClick are stable/unchanged, this entire card skips recomposition.
    Column(Modifier.clickable(onClick = onClick)) {
        Text(product.name)
        Text("$${product.price}")
    }
}

// Usage: Stable lambda via remember ensures onClick doesn't change every recomposition.
@Composable
fun ProductList(products: List<Product>) {
    LazyColumn {
        items(products, key = { it.id }) { product ->
            val onClick = remember(product.id) { { navigateToDetail(product.id) } }
            ProductCard(product = product, onClick = onClick)
        }
    }
}
```

***

#### **Q16. How does `LazyColumn` differ from `RecyclerView` in terms of item recycling and composition strategy?**

`LazyColumn` is the Compose equivalent of `RecyclerView`, but it operates on an entirely different paradigm. `RecyclerView` recycles physical `View` instances: when an item scrolls off-screen, its `ViewHolder` is detached, bound to new data, and reattached. This minimizes object allocation but requires boilerplate (`Adapter`, `ViewHolder`, `DiffUtil`, `LayoutManager`). `LazyColumn`, conversely, is a composable that lazily invokes its item content lambdas as the user scrolls. Historically, it did not recycle the underlying composition nodes, meaning new nodes were created for every new item scrolling into view, which could cause performance issues with massive lists. However, modern versions of Compose (1.3+) introduced composition node recycling for `LazyColumn`, bringing its performance much closer to `RecyclerView`. The key difference remains the mental model: `LazyColumn` uses declarative composition—there is no adapter, no `findViewById`, and state is managed via `remember` inside the item lambda (scoped to the item's key). `LazyColumn` also natively supports item animations, spacing, headers, and grids through simple DSL functions (`items`, `stickyHeader`, etc.), whereas `RecyclerView` requires third-party libraries or custom `ItemDecoration`/`ItemAnimator` for similar effects.

```kotlin
// RecyclerView approach (imperative, boilerplate-heavy):
// adapter.submitList(newList); diffUtil.dispatchUpdatesTo(adapter)

// LazyColumn approach (declarative, no adapter needed):
@Composable
fun MessageList(messages: List<Message>, onDelete: (String) -> Unit) {
    LazyColumn(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.spacedBy(8.dp),
        contentPadding = PaddingValues(16.dp)
    ) {
        // Sticky header for date grouping
        messages.groupBy { it.date }.forEach { (date, msgs) ->
            stickyHeader {
                Text(
                    text = date,
                    modifier = Modifier.fillMaxWidth().background(MaterialTheme.colorScheme.surface)
                )
            }
            items(
                items = msgs,
                key = { it.id } // Crucial for state retention and recycling.
            ) { message ->
                var expanded by remember { mutableStateOf(false) }
                MessageCard(
                    message = message,
                    expanded = expanded,
                    onExpand = { expanded = !expanded },
                    onDelete = { onDelete(message.id) }
                )
            }
        }
    }
}

@Composable
fun MessageCard(message: Message, expanded: Boolean, onExpand: () -> Unit, onDelete: () -> Unit) {
    Card(modifier = Modifier.fillMaxWidth().clickable(onClick = onExpand)) {
        Column(Modifier.padding(16.dp)) {
            Text(message.sender, style = MaterialTheme.typography.titleMedium)
            if (expanded) {
                Text(message.body)
                TextButton(onClick = onDelete) { Text("Delete") }
            }
        }
    }
}
```

***

#### **Q17. What is the cost of using `Modifier` chains extensively, and how do you optimize them?**

Every modifier in a chain (`Modifier.fillMaxWidth().padding(16.dp).background(Color.Red).clickable { }`) creates a node in the Modifier tree. When the layout system measures, places, and draws the UI, it traverses this tree. A deeply nested or frequently recreated modifier chain increases allocation pressure and traversal time, especially if the chain is constructed inside a loop or a frequently recomposing lambda. The most impactful optimization is to extract static parts of the chain into a remembered or top-level variable so they are not reallocated on every recomposition. If a modifier chain is identical across many items in a `LazyColumn`, define it outside the item lambda. Additionally, order matters for both semantics and performance: modifiers like `padding` should generally come before `background` if you want the background to fill the padded area, but placing expensive modifiers (like `graphicsLayer`) only where needed reduces render node creation. Avoid creating new `Modifier` instances inside `items` or `Column` blocks unless they depend on dynamic data; instead, apply the base modifier to the container and only override specific properties inside the loop.

```kotlin
// BAD: Allocating a new Modifier chain for EVERY item in a large list.
@Composable
fun BadList(items: List<String>) {
    LazyColumn {
        items(items) {
            Text(
                it,
                modifier = Modifier.fillMaxWidth().padding(16.dp).background(Color.LightGray).clickable { }
            )
        }
    }
}

// GOOD: Extract the common modifier to avoid repeated allocation.
val listItemModifier = Modifier
    .fillMaxWidth()
    .padding(horizontal = 16.dp, vertical = 8.dp)
    .background(MaterialTheme.colorScheme.surfaceVariant, RoundedCornerShape(8.dp))

@Composable
fun GoodList(items: List<String>, onItemClick: (String) -> Unit) {
    LazyColumn {
        items(items, key = { it }) { text ->
            Text(
                text = text,
                modifier = listItemModifier.clickable { onItemClick(text) },
                style = MaterialTheme.typography.bodyLarge
            )
        }
    }
}

// EVEN BETTER: If the clickable behavior is dynamic, remember the combined modifier.
@Composable
fun OptimizedList(items: List<String>, onItemClick: (String) -> Unit) {
    LazyColumn {
        items(items, key = { it }) { text ->
            val modifier = remember(text) {
                listItemModifier.clickable { onItemClick(text) }
            }
            Text(text = text, modifier = modifier)
        }
    }
}
```

***

#### **Q18. When should you use `key` in `LazyColumn` or `LazyRow`, and what happens if you omit it?**

You should use the `key` parameter in `LazyColumn` / `LazyRow` whenever your list items have a stable, unique identifier (such as a database ID or UUID). The `key` serves as the identity of the item for the lazy layout engine. Without it, Compose uses the item's position (index) as its identity. This causes several problems: first, any `remember`ed state inside the item composable is tied to the position, not the data. If the list is reordered, filtered, or items are inserted/deleted, the state will "jump" to the wrong item (e.g., a checkbox checked for item A will appear on item B after a sort). Second, without keys, `LazyColumn` cannot efficiently animate item movements or removals because it sees all changes as simple index shifts rather than identity-based moves. Third, it hampers composition recycling because the framework cannot reliably match old compositions to new data items. Always provide `key` when the list is dynamic, supports reordering, or contains interactive state. For static lists where items never change order or state, omitting `key` is acceptable but still discouraged as a best practice.

```kotlin
data class Task(val id: String, val title: String, val isDone: Boolean)

@Composable
fun TaskList(tasks: List<Task>, onToggle: (String) -> Unit, onDelete: (String) -> Unit) {
    LazyColumn {
        // BAD: No key. If tasks are reordered, 'expanded' state stays at the index, not the task.
        // items(tasks) { task -> ... }

        // GOOD: key = task.id ensures state and composition are bound to the data identity.
        items(
            items = tasks,
            key = { it.id }
        ) { task ->
            // This 'expanded' state is now correctly tied to this specific task ID.
            var expanded by remember { mutableStateOf(false) }

            ListItem(
                headlineContent = { Text(task.title) },
                supportingContent = {
                    if (expanded) Text("ID: ${task.id}")
                },
                leadingContent = {
                    Checkbox(
                        checked = task.isDone,
                        onCheckedChange = { onToggle(task.id) }
                    )
                },
                trailingContent = {
                    IconButton(onClick = { onDelete(task.id) }) {
                        Icon(Icons.Default.Delete, contentDescription = "Delete")
                    }
                },
                modifier = Modifier.clickable { expanded = !expanded }
            )
        }
    }
}

// Animation of item movement also works correctly with keys:
@Composable
fun AnimatedTaskList(tasks: List<Task>) {
    val state = rememberLazyListState()
    LazyColumn(state = state) {
        items(tasks, key = { it.id }) { task ->
            // AnimatedVisibility or Modifier.animateItemPlacement works here
            // because LazyColumn knows the item identity.
            Text(
                task.title,
                modifier = Modifier.animateItemPlacement(tween(300))
            )
        }
    }
}
```

***

#### **Q19. How do you debug and fix recomposition loops or excessive recompositions in a large screen?**

Recomposition loops occur when a composable writes to state during its own composition, which triggers another recomposition, creating an infinite loop. Excessive recompositions happen when large parts of the UI redraw unnecessarily due to unstable parameters or high-level state reads. To debug, first enable Compose Compiler metrics in your build file (`reportsDestination`) to see which functions are skippable versus restartable. In Android Studio, use the Layout Inspector with "Show Recomposition Counts" enabled to visualize which nodes are recomposing and how often. To fix loops, never write to `mutableStateOf` directly during the composition body (outside of event handlers like `onClick`). If you need to derive a value, use `derivedStateOf` to break the reactive chain. To fix excessive recompositions, apply "donut-hole skipping" principles: pass only stable, primitive data to leaf composables, and read state as far down the tree as possible (localizing state reads). If a high-level object changes frequently but a child only cares about one field, read that field inside the child rather than passing the whole object. Use `SideEffect` or `LaunchedEffect` for side effects, not the composition body.

```kotlin
// BAD LOOP: Writing state during composition causes infinite recomposition.
@Composable
fun BadCounter() {
    var count by remember { mutableIntStateOf(0) }
    count++ // DO NOT DO THIS. Triggers recomposition immediately.
    Text("$count")
}

// GOOD: State changes happen in response to events or effects.
@Composable
fun GoodCounter() {
    var count by remember { mutableIntStateOf(0) }
    LaunchedEffect(Unit) {
        while (isActive) {
            delay(1000)
            count++ // Safe: writes happen in a coroutine, not during composition.
        }
    }
    Text("$count")
}

// BAD EXCESSIVE RECOMPOSITION: Parent reads entire ViewModel state and passes object down.
@Composable
fun BadProfileScreen(vm: ProfileViewModel) {
    val state by vm.state.collectAsStateWithLifecycle() // Recomposes on ANY field change
    Header(state.user.name) // Header recomposes even if only 'state.notifications' changed.
    NotificationList(state.notifications)
}

// GOOD: Localize state reads so only affected composables recompose.
@Composable
fun GoodProfileScreen(vm: ProfileViewModel) {
    val name by vm.userName.collectAsStateWithLifecycle()
    val notifications by vm.notifications.collectAsStateWithLifecycle()
    Header(name) // Only recomposes when name changes.
    NotificationList(notifications) // Only recomposes when notifications change.
}

// DEBUG: Add SideEffect logging to trace recompositions in development.
@Composable
fun TracedComposable(name: String) {
    SideEffect {
        Log.d("RecompositionTracer", "$name recomposed at ${System.currentTimeMillis()}")
    }
}
```

***

#### **Q20. Explain the impact of using `BoxWithConstraints` or `SubcomposeLayout` on performance.**

Both `BoxWithConstraints` and `SubcomposeLayout` introduce a **subcomposition** phase during measurement. In standard Compose layout, the composition phase runs first (creating the UI tree), then the measurement phase runs (calculating sizes). `BoxWithConstraints` needs to know the incoming constraints before it can decide what to compose (e.g., choosing between a `Row` and a `Column` based on available width). This means it composes its children *inside* the measure pass, creating a second composition pass for its content. `SubcomposeLayout` generalizes this: it allows you to call `subcompose(slotId) { ... }` during the measure/place block, which is incredibly powerful for custom layouts but inherently more expensive because composition and measurement are interleaved. The impact is most severe inside scrollable containers like `LazyColumn`, where subcomposition can happen for every visible item during every scroll frame, causing jank. You should prefer standard `Layout` or pre-calculated modifiers if you already know the constraints from the parent. Only use `BoxWithConstraints` for top-level responsive switches (e.g., phone vs. tablet layout) and `SubcomposeLayout` for complex custom layouts that absolutely require composition decisions based on measurement results.

```kotlin
// BAD: Using BoxWithConstraints inside a LazyColumn item causes subcomposition per item.
@Composable
fun BadAdaptiveItem(text: String) {
    BoxWithConstraints {
        if (maxWidth > 400.dp) {
            Row { Icon(Icons.Default.Info, null); Text(text) }
        } else {
            Column { Icon(Icons.Default.Info, null); Text(text) }
        }
    }
}

// GOOD: Pass the window size class from the top level and use standard Layout.
@Composable
fun GoodScreen(windowSize: WindowWidthSizeClass) {
    val isExpanded = windowSize == WindowWidthSizeClass.Expanded
    LazyColumn {
        items(100) {
            AdaptiveItem(isExpanded = isExpanded, text = "Item $it")
        }
    }
}

@Composable
fun AdaptiveItem(isExpanded: Boolean, text: String) {
    if (isExpanded) {
        Row(Modifier.fillMaxWidth()) {
            Icon(Icons.Default.Info, null)
            Text(text)
        }
    } else {
        Column {
            Icon(Icons.Default.Info, null)
            Text(text)
        }
    }
}

// SubcomposeLayout: Powerful but expensive. Use only when necessary.
@Composable
fun MeasuredOverlay(content: @Composable () -> Unit, overlay: @Composable (Size) -> Unit) {
    SubcomposeLayout { constraints ->
        val mainPlaceables = subcompose("main") { content() }.map { it.measure(constraints) }
        val maxWidth = mainPlaceables.maxOf { it.width }
        val maxHeight = mainPlaceables.maxOf { it.height }
        val overlayPlaceables = subcompose("overlay") {
            overlay(Size(maxWidth.toDp().value, maxHeight.toDp().value))
        }.map { it.measure(constraints) }

        layout(maxWidth, maxHeight) {
            mainPlaceables.forEach { it.placeRelative(0, 0) }
            overlayPlaceables.forEach { it.placeRelative(0, 0) }
        }
    }
}
```


---

# 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-2.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.
