Jetpack Compose

Anitaa Murthy | Oct 3, 2024 min read

Here is the original article:

🔗 Android Interview Series 2024 — Part 7 (Jetpack Compose)

This article is published on ProAndroidDev and covers essential Android interview topics for 2024. 🚀

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 a WebView inside a Jetpack Compose layout
@Composable
fun WebViewExample(url: String) {
    AndroidView(
        factory = { context ->
            WebView(context).apply {
                settings.javaScriptEnabled = true
                loadUrl(url)
            }
        },
        update = { webView ->
            webView.loadUrl(url) // Allows updating the URL when it changes
        }
    )
}

Embedding Jetpack Compose in XML Layouts:

// Add a ComposeView to your XML layout.
<androidx.compose.ui.platform.ComposeView
    android:id="@+id/compose_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />
  
// In your Activity or Fragment, get a reference to ComposeView and set its content
val composeView = findViewById<ComposeView>(R.id.compose_view)
composeView.setContent {
    Text("Hello from Jetpack Compose!")
}
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?
  1. 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 in Row or Column layouts to distribute space among children based on their weight.
  1. 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.
  1. 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.
  1. Animation modifiers: add animations and transitions to composables.
  • animateContentSize: Automatically animates size changes.
  • graphicsLayer: applies transformations such as scaling, rotation, and translation.
  1. 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: Using Modifier functions like fillMaxWidth(), fillMaxSize(), weight(), and wrapContentSize() 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.
@Composable
fun ResponsiveBoxExample() {
    BoxWithConstraints(
        modifier = Modifier
            .fillMaxSize()
            .background(Color.LightGray)
    ) {
        val boxWidth = maxWidth
        val boxHeight = maxHeight

val dynamicBoxWidth = boxWidth * 0.7f
        val dynamicBoxHeight = boxHeight * 0.3f
        Box(
            modifier = Modifier
                .size(dynamicBoxWidth, dynamicBoxHeight)
                .background(Color.Blue, shape = RoundedCornerShape(8.dp))
                .align(Alignment.Center)
        ) {
            Text(
                text = "70% Width, 30% Height",
                color = Color.White,
                modifier = Modifier.align(Alignment.Center)
            )
        }
    }
}
  • 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).
@Composable
fun ResponsiveLayout() {
    val windowSizeClass = rememberWindowSizeClass()

    when (windowSizeClass.widthSizeClass) {
        WindowWidthSizeClass.Compact -> {
            Column { /* Layout for phones */ }
        }
        WindowWidthSizeClass.Medium -> {
            Row { /* Layout for small tablets */ }
        }
        WindowWidthSizeClass.Expanded -> {
            Row { /* Layout for larger tablets and desktops */ }
        }
    }
}
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.

// Only the Counter composable and its children are 
// recomposed because they directly depend on count.
@Composable
fun Counter() {
   // When count is incremented, the MutableState triggers a change.
    var count by remember { mutableStateOf(0) }
    Button(onClick = { count++ }) {
       // The Text inside the Button updates to show the new 
       // count without redrawing the entire screen.
        Text("Clicked $count times")
    }
}
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 and mutableStateOf, which retain values across recompositions within the same composable.
@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) } // Local state
    Button(onClick = { count++ }) {
        Text("Clicked $count times")
    }
}
  • 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.
@Composable
fun ParentComposable() {
    var sharedCount by remember { mutableStateOf(0) } // Shared state
    ChildComposable(sharedCount, onClick = { sharedCount++ })
}

@Composable
fun ChildComposable(count: Int, onClick: () -> Unit) {
    Button(onClick = onClick) {
        Text("Shared count: $count")
    }
}
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.

@Composable
fun ParentComposable() {
    // count and onIncrement are managed by ParentComposable
    var count by remember { mutableStateOf(0) }
    CounterButton(count = count, onIncrement = { count++ })
}

// CounterButton becomes a stateless, reusable composable 
// that simply displays the count and triggers the onIncrement 
// function when clicked
@Composable
fun CounterButton(count: Int, onIncrement: () -> Unit) {
    Button(onClick = onIncrement) {
        Text("Clicked $count times")
    }
}
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.
@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) } // Retains value across recompositions
    Button(onClick = { count++ }) {
        Text("Clicked $count times")
    }
}
15. Explain rememberSaveable. How is it different from remember?

rememberSaveable works similarly to remember, 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 (or SavedStateHandle). This enables it to restore the values after configuration changes, though it may incur slight overhead for storing and retrieving data.

@Composable
fun TextInputExample() {
    var text by rememberSaveable { mutableStateOf("") }
    TextField(value = text, onValueChange = { text = it })
}
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 a MutableState 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 the mutableStateOf function. This function returns an instance of MutableState that holds the initial value and updates the value whenever it changes.
@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }
    Button(onClick = { count++ }) {
        Text("Clicked $count times")
    }
}
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.
@Composablefun MultiComponentWithDerivedState() {
    val items = remember { mutableStateListOf(1, 2, 3) }
    var counter by remember { mutableStateOf(0) }
    var text by remember { mutableStateOf("") }
    val sum by remember { derivedStateOf { items.sum() } }

Column {
        Button(onClick = { items.add((1..10).random()) }) {
            Text(text = "Add Item")
        }
        Text(text = "Sum: $sum")
        Button(onClick = { counter++ }) {
            Text(text = "Counter: $counter")
        }
        TextField(value = text, onValueChange = { text = it })
    }
}
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?
  1. 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 in LaunchedEffect is used to identify the LaunchedEffect instance and prevent it from being recomposed unnecessarily.
  • If the value of the key parameter changes, Jetpack Compose will consider the LaunchedEffect instance as a new instance, and will execute the side effect again.
// The LaunchedEffect is triggered whenever viewState changes. 
// When you use the entire viewState object as a key in LaunchedEffect, 
// the coroutine inside LaunchedEffect will be re-launched every time 
// the viewState object changes. This means that even if a property in 
// viewState changes that is not relevant to the logic inside LaunchedEffect, 
// the coroutine will still restart
@Composable
fun UserScreen(viewModel: UserViewModel = viewModel(), onUserLoaded: (User) -> Unit) {
    val viewState by viewModel.viewState.collectAsStateLifecycle()

LaunchedEffect(viewState) {
        when {
            viewState.errorMessage != null -> {
                // Show a Snackbar when an error occurs
                SnackbarHostState().showSnackbar(viewState.errorMessage!!)
            }
            viewState.user != null -> {
                // Handle successful user loading, e.g., navigate to a new screen
                onUserLoaded(viewState.user)
            }
        }
    }
}
  1. 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 with DisposableEffect 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.

It’s often used to manage resources that need explicit cleanup, like registering/unregistering listeners. A key point with DisposableEffect 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.

@Composable
fun CustomStatusBarScreen() {
    // Use LocalContext to access the current Activity's window
    val context = LocalContext.current
    val window = (context as? Activity)?.window

    // Define the custom color for the status bar
    val customColor = Color(0xFF6200EE) // Example: Purple color

    // Use DisposableEffect to update and reset the status bar color
    DisposableEffect(Unit) {
        // Set the custom color for the status bar
        window?.statusBarColor = customColor.toArgb()

        // Ensure that the status bar color is reset when the composable is removed
        onDispose {
            window?.statusBarColor = ContextCompat.getColor(context, R.color.default_status_bar_color)
        }
    }

    // Content of the composable screen
    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text("This screen has a custom status bar color", fontSize = 18.sp)
    }
}
  1. 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.
@Composable
fun LoadDataButton() {
    val scope = rememberCoroutineScope()
    var data by remember { mutableStateOf<String?>(null) }
    
    Button(onClick = {
        scope.launch {
            data = fetchDataFromApi() // Suspend function
        }
    }) {
        Text("Load Data")
    }
    
    data?.let {
        Text("Data: $it")
    }
}
  1. 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.
// Suppose you have a countdown timer that should execute a callback 
// function when the countdown reaches zero. However, if the callback 
// function changes while the timer is running, you want the timer 
// to call the latest version of the callback without restarting the 
// countdown
@Composable
fun CountdownTimer(duration: Long, onTimerFinish: () -> Unit) {
    val currentCallback by rememberUpdatedState(newValue = onTimerFinish)

    LaunchedEffect(duration) {
        // Start countdown
        delay(duration)
        // Call the latest version of the callback without restarting the timer
        currentCallback()
    }
}
  1. 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.
// Imagine you have a UserProfile screen that displays the user's 
// profile information. Every time the composable recomposes (perhaps 
// due to some state change), you want to log an event for analytics 
// purposes. Here’s how you can use SideEffect to achieve this:
@Composable
fun UserProfileScreen(userName: String, userAge: Int) {
    // Display user profile information
    Column {
        Text(text = "Name: $userName")
        Text(text = "Age: $userAge")
    }

    // Use SideEffect to log an analytics event each time this composable recomposes
    SideEffect {
        logAnalyticsEvent("UserProfileScreen", "UserProfileRecomposed")
    }
}

fun logAnalyticsEvent(screenName: String, event: String) {
    // This function would integrate with your analytics service to log the event
    Log.d("Analytics", "Screen: $screenName - Event: $event")
    // Example: AnalyticsService.logEvent(screenName, event)
}
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.

// mutableStateListOf creates a SnapshotStateList, which is automatically tracked by Compose.
// When todos.add("New task") is called, Compose observes this change, 
// triggering recomposition and updating the UI to show the new item.
// The forEach loop displays each item, so adding a new item automatically 
// updates the displayed list without needing additional code to re-render it
@Composable
fun TodoListScreen() {
    val todos = remember { mutableStateListOf("Buy groceries", "Call mom", "Do laundry") }

    Column {
        Button(onClick = { todos.add("New task") }) {
            Text("Add Task")
        }

        todos.forEach { task ->
            Text(task)
        }
    }
}
// mutableStateMapOf creates a SnapshotStateMap, 
// which is automatically tracked by Compose.
// The Switch updates each setting's value directly 
// in the map, and any change to a setting automatically 
// triggers recomposition for the affected part of the UI, 
// reflecting the new setting state immediately.
@Composable
fun SettingsScreen() {
    val settings = remember {
        mutableStateMapOf(
            "Notifications" to true,
            "Dark Mode" to false,
            "Location Access" to true
        )
    }

    Column {
        settings.forEach { (key, value) ->
            Row {
                Text(text = key)
                Switch(checked = value, onCheckedChange = { newValue ->
                    settings[key] = newValue
                })
            }
        }
    }
}
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.

@Composable
fun SearchBar(onSearch: (String) -> Unit) {
    var query by remember { mutableStateOf("") }

    // Use LaunchedEffect with snapshotFlow to observe query changes
    LaunchedEffect(query) {
        snapshotFlow { query }
            .debounce(300) // Only emit the value if no other input arrives within 300ms
            .filter { it.isNotEmpty() } // Only search for non-empty queries
            .collect { searchQuery ->
                onSearch(searchQuery) // Perform search or API call
            }
    }

    TextField(
        value = query,
        onValueChange = { query = it },
        modifier = Modifier.fillMaxWidth(),
        placeholder = { Text("Search...") }
    )
}
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>
// A common use case for produceState is to fetch data from an asynchronous 
// source, such as a network API or a database, and provide that data as state 
// to the UI. For example, consider an app that displays weather information 
// for a city. You could use produceState to fetch and update weather data when 
// the city changes or the screen is recomposed.
@Composable
fun WeatherScreen(cityName: String) {
    // Use produceState to fetch weather data based on the city name
    val weatherState = produceState<Weather?>(initialValue = null, cityName) {
        // Fetch data for the city asynchronously
        value = fetchWeatherData(cityName)
    }

    // Display the data
    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        when (val weather = weatherState.value) {
            null -> CircularProgressIndicator() // Show a loading spinner while data is being fetched
            else -> Text("Temperature in $cityName: ${weather.temperature}°C")
        }
    }
}

// Hypothetical suspend function to fetch weather data from an API
suspend fun fetchWeatherData(cityName: String): Weather {
    // Simulate network request
    delay(1000)
    return Weather(cityName, temperature = (15..30).random())
}

data class Weather(val city: String, val temperature: Int)
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.

import androidx.compose.runtime.*
import androidx.compose.material3.Text
import androidx.compose.material3.Button
import androidx.compose.material3.Column
import androidx.compose.ui.tooling.preview.Preview

// Define a CompositionLocal
val LocalUserTheme = compositionLocalOf { "Light" }  // Default theme is "Light"

@Composable
fun MyApp() {
    // Provide a custom value for the CompositionLocal
    CompositionLocalProvider(LocalUserTheme provides "Dark") {
        MyScreen()
    }
}

@Composable
fun MyScreen() {
    // Access the CompositionLocal value
    val userTheme = LocalUserTheme.current
    Column {
        Text("Current Theme: $userTheme")
        Button(onClick = {}) {
            Text("Change Theme")
        }
    }
}

@Preview(showBackground = true)
@Composable
fun PreviewMyApp() {
    MyApp()
}
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.

// Imagine you want to define a primary color that all components
// in a screen can access and use.
// Define a CompositionLocal with a default primary color
val LocalPrimaryColor = compositionLocalOf { Color.Black }

@Composable
fun ThemedScreen() {
    // Provide a specific color within this composable's scope
    CompositionLocalProvider(LocalPrimaryColor provides Color(0xFF6200EE)) {
        Column(
            modifier = Modifier
                .fillMaxSize()
                .background(LocalPrimaryColor.current),
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Text("Hello, World!", color = LocalPrimaryColor.current)
            CustomButton()
        }
    }
}

@Composable
fun CustomButton() {
    Button(
        onClick = { /* Do something */ },
        colors = ButtonDefaults.buttonColors(backgroundColor = LocalPrimaryColor.current)
    ) {
        Text("Click Me")
    }
}
  • 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.
// Suppose you want to provide access to SharedPreferences across multiple 
// composables to read user preferences.
// Define a static CompositionLocal for SharedPreferences
val LocalSharedPreferences = staticCompositionLocalOf<SharedPreferences> {
    error("No SharedPreferences provided")
}

@Composable
fun MyApp(sharedPreferences: SharedPreferences) {
    CompositionLocalProvider(LocalSharedPreferences provides sharedPreferences) {
        // Now any child composable can access LocalSharedPreferences.current
        UserProfileScreen()
    }
}

@Composable
fun UserProfileScreen() {
    val sharedPreferences = LocalSharedPreferences.current
    val userName = sharedPreferences.getString("user_name", "Guest")

    Text(text = "Welcome, $userName!")
}

Jetpack Compose also provides several predefined CompositionLocal objects for common scenarios, like accessing theme values, layout direction, and text input service.

// In a dark mode-compatible app, you might want to access the 
// current color scheme or typography without manually passing it down.
@Composable
fun ThemedText() {
    // Access LocalContentColor and LocalTextStyle provided by the MaterialTheme
    Text(
        text = "Hello, Theme!",
        color = LocalContentColor.current,
        style = LocalTextStyle.current
    )
}
25. How can we manage navigation using Composition Local?
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import androidx.navigation.compose.*
import androidx.compose.ui.tooling.preview.Preview

// Define a CompositionLocal for NavController
val LocalNavController = compositionLocalOf<NavController?> { null }

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApp()
        }
    }
}

@Composable
fun MyApp() {
    // Create a NavController for the app
    val navController = rememberNavController()

    // Provide the NavController to the CompositionLocal scope
    CompositionLocalProvider(LocalNavController provides navController) {
        // Set up the NavHost with two screens: Home and Details
        NavHost(navController = navController, startDestination = "home") {
            composable("home") { HomeScreen() }
            composable("details") { DetailsScreen() }
        }
    }
}

@Composable
fun HomeScreen() {
    // Access the NavController from CompositionLocal
    val navController = LocalNavController.current
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(text = "Home Screen", fontSize = 24.sp, fontWeight = FontWeight.Bold)
        Spacer(modifier = Modifier.height(16.dp))
        Button(onClick = { navController?.navigate("details") }) {
            Text("Go to Details")
        }
    }
}

@Composable
fun DetailsScreen() {
    // Access the NavController from CompositionLocal
    val navController = LocalNavController.current
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(text = "Details Screen", fontSize = 24.sp, fontWeight = FontWeight.Bold)
        Spacer(modifier = Modifier.height(16.dp))
        Button(onClick = { navController?.navigate("home") }) {
            Text("Back to Home")
        }
    }
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    MyApp()
}
26. How can we dynamically switch themes with the help of CompositionLocal?
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.tooling.preview.Preview

// Define CompositionLocal for theme colors
val LocalAppColors = compositionLocalOf { lightColors() }

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApp()
        }
    }
}

// Light and dark theme color definitions
fun lightColors() = AppColors(
    background = Color.White,
    content = Color.Black,
    buttonColor = Color(0xFF6200EE)
)

fun darkColors() = AppColors(
    background = Color.Black,
    content = Color.White,
    buttonColor = Color(0xFFBB86FC)
)

// Data class for holding color values
data class AppColors(
    val background: Color,
    val content: Color,
    val buttonColor: Color
)

@Composable
fun MyApp() {
    // State to track the current theme
    var isDarkTheme by remember { mutableStateOf(false) }
    // Update the theme colors based on the isDarkTheme flag
    val colors = if (isDarkTheme) darkColors() else lightColors()

    // Provide the theme colors to the CompositionLocal
    CompositionLocalProvider(LocalAppColors provides colors) {
        // Themed content that reacts to theme changes
        Surface(
            modifier = Modifier.fillMaxSize(),
            color = LocalAppColors.current.background
        ) {
            Column(
                modifier = Modifier
                    .fillMaxSize()
                    .padding(16.dp),
                verticalArrangement = Arrangement.Center,
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                Text(
                    text = "Dynamic Theme Switching",
                    color = LocalAppColors.current.content,
                    fontSize = 24.sp,
                    fontWeight = FontWeight.Bold
                )
                Spacer(modifier = Modifier.height(16.dp))
                Button(
                    onClick = { isDarkTheme = !isDarkTheme },
                    colors = ButtonDefaults.buttonColors(
                        backgroundColor = LocalAppColors.current.buttonColor
                    )
                ) {
                    Text("Switch Theme")
                }
            }
        }
    }
}
27. How can we manage authenticated state of a user with the help of CompositionLocal?
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp

// Define a CompositionLocal for managing the user authentication state
val LocalAuthState = compositionLocalOf<AuthState> { AuthState.Unauthenticated }

sealed class AuthState {
    object Unauthenticated : AuthState()
    data class Authenticated(val username: String) : AuthState()
}

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApp()
        }
    }
}

@Composable
fun MyApp() {
    // State to track if the user is authenticated and the current username
    var authState by remember { mutableStateOf<AuthState>(AuthState.Unauthenticated) }

    // Provide the auth state to the CompositionLocal
    CompositionLocalProvider(LocalAuthState provides authState) {
        // Display either the login screen or the home screen based on auth state
        when (val currentAuthState = LocalAuthState.current) {
            is AuthState.Authenticated -> HomeScreen(
                username = currentAuthState.username,
                onLogout = { authState = AuthState.Unauthenticated }
            )
            AuthState.Unauthenticated -> LoginScreen(onLogin = { username ->
                authState = AuthState.Authenticated(username)
            })
        }
    }
}

@Composable
fun LoginScreen(onLogin: (String) -> Unit) {
    var username by remember { mutableStateOf("") }

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(text = "Login Screen", style = MaterialTheme.typography.titleLarge)
        Spacer(modifier = Modifier.height(16.dp))
        TextField(
            value = username,
            onValueChange = { username = it },
            label = { Text("Username") }
        )
        Spacer(modifier = Modifier.height(16.dp))
        Button(onClick = { onLogin(username) }) {
            Text("Login")
        }
    }
}

@Composable
fun HomeScreen(username: String, onLogout: () -> Unit) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(text = "Welcome, $username!", style = MaterialTheme.typography.titleLarge)
        Spacer(modifier = Modifier.height(16.dp))
        Button(onClick = onLogout) {
            Text("Logout")
        }
    }
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    MyApp()
}
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. The by keyword is often used with mutableStateOf or remember 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: The remember function caches values across recompositions, preventing the need to recalculate values that don’t change.
  • rememberSaveable extends remember 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 and LazyRow is essential. Unlike Column and Row, they only render visible items, which conserves memory and improves performance.
  • snapshotFlow efficiently converts Compose state into a Kotlin Flow. 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.
// Simulated data classes
data class UserProfile(val id: Int, val name: String, val bio: String)
data class Friend(val id: Int, val name: String)

// Simulated external data source
suspend fun fetchFriends(): List<Friend> {
    delay(1000) // Simulate network delay
    return listOf(
        Friend(1, "Alice"),
        Friend(2, "Bob"),
        Friend(3, "Charlie")
    )
}

// MainActivity to host the app content
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApp()
        }
    }
}

@Composable
fun MyApp() {
    // Remember theme toggle state with rememberSaveable to survive configuration changes
    var isDarkTheme by rememberSaveable { mutableStateOf(false) }

    val backgroundColor = if (isDarkTheme) Color.DarkGray else Color.White
    val contentColor = if (isDarkTheme) Color.White else Color.Black

    // Provide theme dynamically with CompositionLocal
    CompositionLocalProvider(
        LocalContentColor provides contentColor,
        LocalBackgroundColor provides backgroundColor
    ) {
        Surface(
            modifier = Modifier
                .fillMaxSize()
                .background(LocalBackgroundColor.current)
        ) {
            UserProfileScreen(
                UserProfile(1, "John Doe", "Loves Compose!"),
                isDarkTheme,
                onToggleTheme = { isDarkTheme = !isDarkTheme }
            )
        }
    }
}

// Custom CompositionLocals
val LocalBackgroundColor = compositionLocalOf { Color.White }
val LocalContentColor = compositionLocalOf { Color.Black }

@Composable
fun UserProfileScreen(user: UserProfile, isDarkTheme: Boolean, onToggleTheme: () -> Unit) {
    var friends by remember { mutableStateOf<List<Friend>>(emptyList()) }
    val friendListState = rememberLazyListState()
    var isExpanded by remember { mutableStateOf(false) }

    // Fetch friends once on composition
    LaunchedEffect(Unit) {
        friends = fetchFriends()
    }

    Column(
        modifier = Modifier.padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        // Profile info with animation on size
        val size by animateDpAsState(targetValue = if (isExpanded) 150.dp else 100.dp)
        ProfilePicture(
            modifier = Modifier.size(size),
            color = LocalContentColor.current,
            onClick = { isExpanded = !isExpanded }
        )
        Text(user.name, color = LocalContentColor.current, fontSize = 24.sp)
        Text(user.bio, color = LocalContentColor.current, fontSize = 16.sp)

        Spacer(modifier = Modifier.height(16.dp))
        Button(onClick = onToggleTheme) {
            Text(if (isDarkTheme) "Switch to Light Mode" else "Switch to Dark Mode")
        }

        Spacer(modifier = Modifier.height(24.dp))
        Text("Friends", color = LocalContentColor.current, fontSize = 20.sp)
        
        // LazyColumn for friends list with stable key for optimization
        LazyColumn(state = friendListState, modifier = Modifier.fillMaxWidth()) {
            items(friends, key = { it.id }) { friend ->
                FriendItem(friend = friend)
            }
        }

        // Use snapshotFlow to observe scroll position
        LaunchedEffect(friendListState) {
            snapshotFlow { friendListState.firstVisibleItemIndex }
                .distinctUntilChanged()
                .filter { it >= friends.size - 2 }
                .collect { 
                    // Load more friends if available, or show a message
                }
        }
    }
}

@Composable
fun ProfilePicture(modifier: Modifier, color: Color, onClick: () -> Unit, shape: Shape = CircleShape) {
    Box(
        modifier = modifier
            .background(color, shape)
            .clickable(onClick = onClick),
        contentAlignment = Alignment.Center
    ) {
        Text("Profile", color = Color.White)
    }
}

@Composable
fun FriendItem(friend: Friend) {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .padding(vertical = 8.dp),
        verticalAlignment = Alignment.CenterVertically
    ) {
        Box(
            modifier = Modifier
                .size(40.dp)
                .background(LocalContentColor.current, CircleShape)
        )
        Spacer(modifier = Modifier.width(8.dp))
        Text(friend.name, color = LocalContentColor.current, fontSize = 18.sp)
    }
}

@Preview(showBackground = true)
@Composable
fun PreviewMyApp() {
    MyApp()
}
30. Share an example of how we can manage state using ViewModel and LiveData in Compose.
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.viewModel

// Main Activity
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApp()
        }
    }
}

// App Composable that observes ViewModel state
@Composable
fun MyApp(viewModel: CounterViewModel = viewModel()) {
    // Observe LiveData from the ViewModel
    val count by viewModel.count.observeAsState(0)
    CounterScreen(count = count, onIncrement = viewModel::increment)
}

// ViewModel with LiveData
class CounterViewModel : ViewModel() {
    // MutableLiveData to manage the count state
    private val _count = MutableLiveData(0)
    val count: LiveData<Int> = _count

    // Function to increment count
    fun increment() {
        _count.value = (_count.value ?: 0) + 1
    }
}

// CounterScreen Composable that displays the count and increment button
@Composable
fun CounterScreen(count: Int, onIncrement: () -> Unit) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(text = "Count: $count", style = MaterialTheme.typography.titleLarge)
        Spacer(modifier = Modifier.height(16.dp))
        Button(onClick = onIncrement) {
            Text("Increment")
        }
    }
}

@Preview(showBackground = true)
@Composable
fun PreviewMyApp() {
    MyApp()
}
31. Share an example of how we can manage state using ViewModel and StateFlow in Compose.
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApp()
        }
    }
}

// ViewModel to manage state using StateFlow
class CounterViewModel : ViewModel() {

    // MutableStateFlow to hold the count value
    private val _count = MutableStateFlow(0) // Initial value is 0

    // Expose StateFlow to prevent external modification of _count
    val count: StateFlow<Int> = _count.asStateFlow()

    // Function to increment count
    fun increment() {
        _count.update { it + 1 }
    }

    // Function to decrement count
    fun decrement() {
        _count.update { it - 1 }
    }
}

@Composable
fun MyApp() {
    // Retrieve ViewModel using viewModel() delegate
    val viewModel: CounterViewModel = viewModel()

    // Collect the count value as State in Compose
    val count by viewModel.count.collectAsState()

    // UI layout for the counter
    CounterScreen(count = count, onIncrement = viewModel::increment, onDecrement = viewModel::decrement)
}

@Composable
fun CounterScreen(count: Int, onIncrement: () -> Unit, onDecrement: () -> Unit) {
    // UI for displaying and updating the counter
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(text = "Counter: $count", style = MaterialTheme.typography.titleLarge)
        
        Spacer(modifier = Modifier.height(16.dp))
        
        Row {
            Button(onClick = onDecrement, modifier = Modifier.padding(end = 8.dp)) {
                Text(text = "Decrement")
            }
            Button(onClick = onIncrement) {
                Text(text = "Increment")
            }
        }
    }
}
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.

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            LazyListExample()
        }
    }
}

@Composable
fun LazyListExample() {
    val items = (1..1000).map { "Item $it" } // A large list of 1000 items

    LazyColumn(
        modifier = Modifier.fillMaxSize(),
        contentPadding = PaddingValues(16.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        items(items) { item ->
            ListItem(text = item)
        }
    }
}

@Composable
fun ListItem(text: String) {
    Surface(
        modifier = Modifier
            .fillMaxWidth()
            .padding(8.dp),
        color = MaterialTheme.colorScheme.primary,
        shadowElevation = 4.dp
    ) {
        Text(
            text = text,
            modifier = Modifier.padding(16.dp),
            color = MaterialTheme.colorScheme.onPrimary
        )
    }
}
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 and Column are great for positioning items horizontally or vertically relative to each other. You can adjust Arrangement and Alignment 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 traditional ConstraintLayout in XML.
@Composable
fun ConstraintLayoutExample() {
// ConstraintLayout allows positioning Button 
// below Text by linking button's top to text's bottom.  
    ConstraintLayout(
        modifier = Modifier.fillMaxSize()
    ) {
        // Create references for composables
        val (text, button) = createRefs()

        Text(
            text = "Hello, Compose!",
            modifier = Modifier.constrainAs(text) {
                top.linkTo(parent.top, margin = 16.dp)
                // aligns the button horizontally with
                // the start of the text, achieving 
               // relative positioning.
                start.linkTo(parent.start, margin = 16.dp)
            }
        )

        Button(
            onClick = {},
            modifier = Modifier.constrainAs(button) {
                top.linkTo(text.bottom, margin = 16.dp) // Positioned below text
                start.linkTo(text.start)
            }
        ) {
            Text("Click Me")
        }
    }
}
Thanks for reading!

Hope you find this useful. This is just a list of questions I personally found useful in interviews. This list is by no means exhaustive. Let me know your thoughts in the responses. Happy coding!

Paragraph

Xerum, quo qui aut unt expliquam qui dolut labo. Aque venitatiusda cum, voluptionse latur sitiae dolessi aut parist aut dollo enim qui voluptate ma dolestendit peritin re plis aut quas inctum laceat est volestemque commosa as cus endigna tectur, offic to cor sequas etum rerum idem sintibus eiur? Quianimin porecus evelectur, cum que nis nust voloribus ratem aut omnimi, sitatur? Quiatem. Nam, omnis sum am facea corem alique molestrunt et eos evelece arcillit ut aut eos eos nus, sin conecerem erum fuga. Ri oditatquam, ad quibus unda veliamenimin cusam et facea ipsamus es exerum sitate dolores editium rerore eost, temped molorro ratiae volorro te reribus dolorer sperchicium faceata tiustia prat.

Itatur? Quiatae cullecum rem ent aut odis in re eossequodi nonsequ idebis ne sapicia is sinveli squiatum, core et que aut hariosam ex eat.

Blockquotes

The blockquote element represents content that is quoted from another source, optionally with a citation which must be within a footer or cite element, and optionally with in-line changes such as annotations and abbreviations.

Blockquote without attribution

Tiam, ad mint andaepu dandae nostion secatur sequo quae. Note that you can use Markdown syntax within a blockquote.

Blockquote with attribution

Don’t communicate by sharing memory, share memory by communicating.

Rob Pike1

Tables

Tables aren’t part of the core Markdown spec, but Hugo supports supports them out-of-the-box.

Name Age
Bob 27
Alice 23

Inline Markdown within tables

Inline    Markdown    In    Table
italics bold strikethrough    code

Code Blocks

Code block with backticks

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Example HTML5 Document</title>
  </head>
  <body>
    <p>Test</p>
  </body>
</html>

Code block indented with four spaces

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Example HTML5 Document</title>
</head>
<body>
  <p>Test</p>
</body>
</html>

Code block with Hugo’s internal highlight shortcode

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Example HTML5 Document</title>
</head>
<body>
  <p>Test</p>
</body>
</html>

List Types

Ordered List

  1. First item
  2. Second item
  3. Third item

Unordered List

  • List item
  • Another item
  • And another item

Nested list

  • Item
    1. First Sub-item
    2. Second Sub-item

Headings

The following HTML <h1><h6> elements represent six levels of section headings. <h1> is the highest section level while <h6> is the lowest.

H1

H2

H3

H4

H5
H6

Other Elements — abbr, sub, sup, kbd, mark

GIF is a bitmap image format.

H2O

Xn + Yn = Zn

Press CTRL+ALT+Delete to end the session.

Most salamanders are nocturnal, and hunt for insects, worms, and other small creatures.


  1. The above quote is excerpted from Rob Pike’s talk during Gopherfest, November 18, 2015. ↩︎