This is Part 7 of the android interview question series. This part will focus on Jetpack Compose.
1. What is Jetpack Compose?
Jetpack Compose is Android’s recommended modern toolkit for building native UI. It simplifies and accelerates UI development on Android. Jetpack Compose is declarative programming, which means you can describe your user interface by invoking a set of composables, which is vastly different from the traditional way of imperative UI design.
2. How does Jetpack Compose differ from XML-based UI?
- XML-based UI follows an imperative approach, where you define the UI layout in XML and then programmatically change its properties in the Activity or Fragment as the state changes. Jetpack Compose uses a declarative approach. You define what the UI should look like based on the current state. When the state changes, the UI automatically updates to reflect those changes without requiring manual intervention.
- Since Jetpack Compose reduces the need for XML and reduces code duplication, you can achieve more with less code. This leads to fewer errors and a more maintainable codebase.
- Reusability is simpler due to composable functions. You can create UI components as functions with the
@Composable
annotation and reuse them across different parts of the app, easily adding parameters for customization. - Android Studio provides powerful tools for Jetpack Compose, like live previews, which allow you to see how your UI looks in real-time as you code.
3. How can we use traditional android views and compose together?
Embedding XML View insider Jetpack Compose: We can embed a traditional Android View inside a Jetpack Compose layout using the AndroidView
composable. This allows to use any existing Android View component within a Compose UI.
Embedding Jetpack Compose in XML Layouts:
4. What is a Composable
function, and how do you define one?
A Composable function is a fundamental building block in Jetpack Compose. It’s a special function that defines a piece of UI in a declarative way. By marking a function with @Composable
, you make it possible for Jetpack Compose to track and manage the UI it represents, automatically handling updates whenever the underlying data or state changes.
@Composable
fun Greeting(name: String) {
Text(text = "Hello, $name!")
}
5. Explain the Jetpack Compose Lifecycle.
The Jetpack Compose Lifecycle is similar to the traditional Android lifecycle but has unique characteristics due to its declarative, reactive nature.
- Composition is the initial phase where the UI elements are created for the first time.
- This phase only occurs once for a given part of the UI (unless it needs to be recreated). Once composed, the UI stays on the screen until it is either updated (via recomposition) or removed (via disposal).
- When a composable function is called for the first time, Jetpack Compose: builds the UI tree by evaluating the composable functions and creating UI elements and adds the resulting UI elements to the screen, establishing the initial view structure.
- Recomposition is the phase where Jetpack Compose updates the UI in response to state changes.
- During recomposition, only the functions affected by the state changes are re-evaluated.
- If no state changes occur, Compose will not recompose.
- Recomposition happens whenever a value that a composable depends on changes.
- Disposal is the phase where Compose removes composable functions that are no longer needed from the UI.
- This typically happens when a composable: goes out of scope due to a change in the UI tree (e.g., navigating away from a screen or conditionally removing a component) or is replaced by another composable.
- During the disposal phase, Compose: cleans up resources associated with the composable, such as cancelling coroutines, releasing listeners, or disposing of state objects and executes
DisposableEffect
cleanup code if defined, ensuring no resources are left hanging.
6. What is a Modifier
in Jetpack Compose?
A Modifier in Jetpack Compose is a powerful and flexible tool used to modify the appearance, behavior, and layout of composable functions. Modifiers are essential for adding properties like padding, size, background, click actions, and layout adjustments to composable elements without altering the composable function itself.
- Modifiers are stateless. They do not hold or manage state.
- Modifiers are chainable. They can be chained to apply multiple properties sequentially, creating a flexible way to build complex UI behaviors.
- Modifiers are reusable. They are designed to be highly reusable, allowing you to define them once and apply them to multiple composables.
7. What are the different types of Modifier
?
Layout modifiers: Layout modifiers control the size, padding, alignment, and general layout behavior of a composable.
- padding: Adds padding around a composable.
- fillMaxSize / fillMaxWidth / fillMaxHeight: Makes the composable fill the available space.
- size: Sets an explicit width and height.
- wrapContentSize: Wraps the composable’s size to its content and positions it within the available space.
align()
: modifier specifies the alignment of a composable within its parent layout.weight()
: is used inRow
orColumn
layouts to distribute space among children based on their weight.
Appearance modifiers: Appearance modifiers help you modify the look of composables by adding background colors, borders, and opacity.
- background: Sets a background color.
- border: Adds a border around the composable.
- alpha: Adjusts the transparency of a composable.
- clip: clips the composable to a specified shape.
- shadow: adds a shadow effect to a composable.
Behaviour modifiers: Behavior modifiers allow you to add interactivity, such as click handling, scroll behavior, and gestures.
- clickable: Makes the composable respond to click events.
- scrollable: Adds scroll behavior (e.g., for custom scrollable components).
- toggleable: adds toggle behavior, useful for creating switch-like components.
- draggable: allows dragging gestures on the composable.
Animation modifiers: add animations and transitions to composables.
- animateContentSize: Automatically animates size changes.
- graphicsLayer: applies transformations such as scaling, rotation, and translation.
Custom modifiers: You can create custom modifiers by defining extension functions on Modifier
. This is useful for applying a specific combination of modifiers that you might use frequently.
8. How to create Responsive Layouts with Jetpack Compose?
- Use
Modifier
with Adaptive Sizing: UsingModifier
functions likefillMaxWidth()
,fillMaxSize()
,weight()
, andwrapContentSize()
allows your composables to adapt to the available screen space. - Responsive Layouts with
ConstraintLayout
:ConstraintLayout
allows you to create more complex responsive layouts by defining constraints between elements, similar to XML-based ConstraintLayout in Android. BoxWithConstraints
allows you to access the constraints of the available space, enabling you to create conditional layouts based on the screen size or orientation.
- Compose provides
WindowSizeClass
as a way to categorize screen sizes, making it easy to switch layouts based on the type of device (compact, medium, expanded).
9. How do you handle orientation changes in Jetpack Compose?
Orientation changes in Jetpack Compose are handled automatically by recomposing the UI based on the new configuration. Composable functions that define the UI layout and behavior will be recomposed with the updated configuration, allowing the UI to adapt to the new orientation.
10. How does Recomposition work in Jetpack Compose?
Recomposition is the process by which Jetpack Compose updates parts of the UI when there is a change in state. When a state variable (like a MutableState
) changes, Jetpack Compose identifies the composables that depend on that state and re-runs only those composables, updating the UI accordingly. This minimizes the work needed to keep the UI in sync with the underlying data, which improves performance.
11. What is State in Jetpack Compose?
State in Jetpack Compose represents data that can change over time and that Compose uses to update the UI when it changes. It allows the UI to automatically respond to changes in underlying data.
12. What are the two different types of state?
- Local State: Local state is the state managed within a single composable function. It’s typically used for UI elements that don’t need to share their state with other parts of the UI. Local state is created using
remember
andmutableStateOf
, which retain values across recompositions within the same composable.
- Shared State: In this pattern, the state is moved up to a shared parent component, making it easier to manage across different parts of the UI.
13. What is state hoisting?
State hoisting is a design pattern in Jetpack Compose that involves moving (or “hoisting”) state out of a composable function and into its parent composable. This approach makes the state “shared” between composables and allows for better reusability, testing, and separation of concerns.
14. What is the purpose of remember
in Jetpack Compose?
- The
remember
function in Jetpack Compose is used to store a value across recompositions, allowing the value to persist without resetting every time the composable function is recomposed. - When you use
remember
, Compose caches the value during the initial composition. During recomposition, Compose checks the cache and reuses the stored value instead of recalculating or reinitializing it. However, if the composable leaves the composition (like when navigating away from a screen), the value is cleared.
15. Explain rememberSaveable
. How is it different from remember
?
rememberSaveable
works similarly toremember
, but it preserves its state across configuration changes like screen rotations. It’s useful for UI elements like text fields that need to retain state when the device orientation changes.rememberSaveable
: Saves the values in the bundle of the saved instance state (orSavedStateHandle
). This enables it to restore the values after configuration changes, though it may incur slight overhead for storing and retrieving data.
16. How does MutableState
work in Jetpack Compose?
MutableState
is an observable data holder that allows composables to react to changes in state automatically. When the value of aMutableState
object changes, Jetpack Compose triggers a recomposition for any composables that read that state, updating the UI to reflect the new data.MutableState
is typically created using themutableStateOf
function. This function returns an instance ofMutableState
that holds the initial value and updates the value whenever it changes.
17. Explain the concept of Derived States in Compose.
- Derived State is a concept used to create a new state based on one or more existing states. It allows you to compute values based on other states, updating only when the underlying state(s) change.
- The
derivedStateOf
function is used to create derived states in Compose. This function takes a lambda that computes the derived value and only recomposes when the result of the calculation changes. derivedStateOf
works by observing the input state(s) used within its lambda function. When any of the observed input states change, Compose re-evaluates the lambda.
18. What are SideEffects
in Jetpack Compose?
In Compose, a side-effect refers to any change in the app’s state that occurs outside the scope of a composable function. Side effects should be executed in a way that respects the composable lifecycle to avoid unexpected behaviors, like duplicate network requests on recomposition. Side effects ensure that actions occur only when necessary and not during every recomposition, keeping the UI efficient and consistent.
19. Explain the different SideEffects
in Jetpack Compose?
LaunchedEffect
: is used to run suspend functions within the lifecycle of a composable.
- It triggers a coroutine when the composable enters the composition, making it ideal for tasks like fetching data or handling side-effects based on changes in state.
- The
key
parameter inLaunchedEffect
is used to identify theLaunchedEffect
instance and prevent it from being recomposed unnecessarily. - If the value of the
key
parameter changes, Jetpack Compose will consider theLaunchedEffect
instance as a new instance, and will execute the side effect again.
DisposableEffect
: s used for side effects that require setup and cleanup when the composable enters and exits the composition. It’s often used to manage resources that need explicit cleanup, like registering/unregistering listeners. A key point withDisposableEffect
is that it allows you to add and remove observers or listeners in a safe manner that is tied directly to the composable’s lifecycle. This helps prevent memory leaks and ensures that resources are cleaned up when no longer needed.
rememberCoroutineScope
: When you need a coroutine to start based on a user action, such as a button click,rememberCoroutineScope
is useful. It provides a scope tied to the composable’s lifecycle, ensuring the coroutine cancels if the composable leaves the composition.
rememberUpdatedState
: is to keep an updated reference to a value within long-lived or side-effect composables, like LaunchedEffect
or DisposableEffect
, without restarting them when the value changes.
- It effectively “pins” the latest value, ensuring that ongoing effects can access it without triggering recompositions or re-running the effect.
- This approach is particularly useful when you have a callback or lambda function passed into a composable that may change over time. You may not want to restart the entire effect when the callback changes, especially if the effect is managing a complex operation like a long-running coroutine.
SideEffect
runs non-suspendable side effects during each recomposition. It allows you to perform actions that don’t require any cleanup but need to execute whenever a specific recomposition happens. Examples include logging, debugging, or updating external objects that are not tied to Compose’s lifecycle.
20. What are SnapshotStateList
and SnapshotStateMap
SnapshotStateList
and SnapshotStateMap
are special types of collections in Jetpack Compose designed to work efficiently with Compose’s state system. These collections are observable, meaning that when their content changes, they trigger recomposition in any composables that depend on them. They are useful for managing lists and maps in a way that Compose can track changes and update the UI accordingly.
21. What is snapshotFlow
, and when would you use it?
snapshotFlow
converts state changes within the Compose snapshot system into a Kotlin Flow
. It allows you to observe changes to Compose state values in a coroutine-based Flow
format, which can then be collected and transformed asynchronously. This is particularly useful when you need to react to state changes in a non-composable function or want to combine, debounce, throttle, or filter state updates in a coroutine context.
22. Describe produceState
.
produceState
is used to convert external state, such as data from a network or database, into Compose state. It launches a coroutine that updates the state as necessary. This is particularly useful for managing state that is derived from external sources, such as fetching data from a remote API or database and then feeding that data into your composable’s state.
produceState(
// The initial value of the state before any data is produced.
initialValue: T,
// he dependency keys that determine when produceState should restart the coroutine.
// If any of the keys change, the coroutine will be re-launched.
vararg keys: Any?,
// A lambda that contains the suspendable code to produce the state.
producer: suspend ProduceScope<T>.() -> Unit
): State<T>
23. Explain CompositionLocal
.
CompositionLocal
provides a mechanism for passing data down through the composition implicitly, without needing to pass it through every composable function. This can be particularly useful when the data is frequently used across many parts of the UI, such as theme-related information (like theme, configuration settings, or dependencies).
- CompositionLocal is similar to dependency injection but is designed specifically for Compose’s composable hierarchy.
- It allows composables to access “ambient” data, meaning data that is globally available within a certain scope but not explicitly passed down through parameters.
- CompositionLocalProvider is used to provide values for these locals, and
CompositionLocal.current
is used to access them.
24. What are the different types of CompositionLocal
Providers?
compositionLocalOf
is the most commonly used provider for creating a CompositionLocal
with a default or fallback value. It’s useful when you want to provide a single value that can be accessed anywhere within the composition tree.
This API allows fine control over recompositions. When the value changes, only the parts of the UI that read this value are recomposed. This makes it ideal for frequently changing data like dynamic themes or user preferences.
staticCompositionLocalOf
is similar to compositionLocalOf
, but it is optimized for static values that do not change during recomposition. This provider type should be used when the value is guaranteed not to change after it has been set. This is commonly used for values that are initialized once, such as a singleton dependency, app-wide configurations, or services like SharedPreferences
.
Jetpack Compose also provides several predefined CompositionLocal
objects for common scenarios, like accessing theme values, layout direction, and text input service.
25. How can we manage navigation using Composition Local?
26. How can we dynamically switch themes with the help of CompositionLocal
?
27. How can we manage authenticated state of a user with the help of CompositionLocal
?
28. Explain the concept of delegation and the by
keyword when working with Jetpack Compose.
- Delegation is a design pattern that allows a class to delegate certain responsibilities to another object or class. This concept is especially useful in Compose when dealing with state management.
- The
by
keyword is used to facilitate delegation, making code more concise and readable. - Property delegation allows a property to be managed by another object. Instead of manually implementing getter and setter logic, you can “delegate” this responsibility to an object that implements the required functionality.
- The
by
keyword in Kotlin specifies that a property’s getter and setter methods are handled by the delegate object. Theby
keyword is often used withmutableStateOf
orremember
to delegate state management, allowing Compose to observe changes to the property and trigger recompositions when the property value changes.
29. What are the different optimisation techniques in Jetpack Compose?
- Using
remember
to Cache Values Across Recompositions: Theremember
function caches values across recompositions, preventing the need to recalculate values that don’t change. rememberSaveable
extendsremember
by preserving values across configuration changes, like screen rotations. It’s especially useful for persisting user-entered text or selected options.- Compose automatically recomposes only the parts of the UI that depend on updated state. However, to optimize performance, it’s helpful to isolate state-dependent parts of your UI within smaller composable functions.
LaunchedEffect
is useful for side effects that need to occur only once or when certain keys change. This prevents re-running the effect during every recomposition, which can be resource-intensive.derivedStateOf
can be used to avoid redundant calculations by caching derived values. It recalculates only when its dependencies change, optimizing performance for derived properties.- When displaying large lists, using
LazyColumn
andLazyRow
is essential. UnlikeColumn
andRow
, they only render visible items, which conserves memory and improves performance. snapshotFlow
efficiently converts Compose state into a KotlinFlow
. This is ideal for handling continuous state updates without triggering recompositions.- Jetpack Compose provides the
animateAsState
functions for smooth animations with minimal recompositions. Use them for animating properties that are lightweight and do not trigger recompositions on every frame. - Using stable data and unique keys in lists helps Compose avoid unnecessary recompositions by ensuring that data changes are detected accurately.
- When managing resources like listeners or other resources tied to the composable lifecycle, use
DisposableEffect
for efficient setup and cleanup. This ensures that resources are freed when the composable leaves the composition.
30. Share an example of how we can manage state using ViewModel
and LiveData
in Compose.
Using ViewModel with LiveData or StateFlow is recommended for managing state across lifecycle events, such as screen rotations, or when state needs to persist beyond the lifecycle of a composable.
31. Share an example of how we can manage state using ViewModel
and StateFlow
in Compose.
32. Explain the concept of lazy composition in Jetpack Compose.
Lazy composition refers to the concept of deferring the composition of UI elements until they are actually needed or visible on the screen. This approach is particularly useful for handling large collections of UI elements, like lists or grids, by only composing the items that are currently in view. Lazy composition helps optimize performance and memory usage by minimizing the number of composable functions that are composed at any given time.
33. What are Recomposition and Skippable Recomposition?
- Recomposition is the process in which a composable function re-executes to reflect changes in the underlying state that it depends on. Recomposition works by tracking state reads within a composable function. When a composable reads a state, Compose “subscribes” to that state, and any changes in the state trigger recomposition of that composable.
- Skippable recomposition is a performance optimization that prevents recomposition of composables when their dependencies haven’t changed.
- Compose can “skip” recomposing certain parts of the UI if it detects that the values the composable depends on have not changed since the last recomposition.
- For a composable to be “skippable,” the values it depends on should be stable. In Kotlin, data marked with
val
and immutable data types are inherently stable. - Jetpack Compose considers stable data to be data that is either immutable or marked with
@Stable
.
34. How to achieve Relative Positioning in Jetpack Compose?
Unlike traditional XML layouts in Android, Jetpack Compose does not have a direct equivalent of RelativeLayout
, but it provides composable functions like Box
, Row
, Column
, ConstraintLayout
, and alignment modifiers to achieve relative positioning.
- Using
Box
for Overlapping Composables:Box
is a layout that allows its children to overlap each other, making it useful for positioning items relative to each other with alignment modifiers. Row
andColumn
are great for positioning items horizontally or vertically relative to each other. You can adjustArrangement
andAlignment
to control the positioning of each child.Modifier.offset
allows you to apply pixel offsets to composables, giving precise control over their position relative to the default layout position.ConstraintLayout
provides advanced positioning features, similar to the traditionalConstraintLayout
in XML.