Compose: Fundamental Concepts

Jorge Luis Castro Medina
7 min readMay 19, 2024

Hello everyone! 👋🏻 In this article, I would like to explore the fundamental concepts of Compose, the UI framework that is revolutionizing Android app development (Jetpack Compose). Additionally, I want to highlight its increasing relevance, especially in cross-platform development with Kotlin Multiplatform + Compose Multiplatform. It is crucial to understand these before starting to code, as they will provide you with a solid foundation to build modern and efficient applications.

Disclaimer

Please note that this article provides an overview of the fundamental principles of Compose and does not delve into implementation details. To explore more detailed aspects, I suggest consulting the official documentation and additional resources on each topic.

What is Compose?

Jetpack Compose is Android’s recommended modern toolkit for building native UI. It simplifies and accelerates UI development on Android. Quickly bring your app to life with less code, powerful tools, and intuitive Kotlin APIs.

For cross-platform development, there is a variant called Compose Multipaltform. Don’t worry, it is the same.

It is a Declarative UI Toolkit for Android and cross-platform development.

  • Declarative UI: Defines the user interface intuitively and in an easy-to-understand manner.
  • Interoperability: Compatible with existing Android code for seamless migration.
  • State Management: Simplifies handling changes in UI in response to events.
  • Performance Optimization: Designed to deliver optimal performance within the application.

Composable functions

In Compose, Composable functions are special functions that allow you to define a user interface in a declarative manner. This means you only need to specify how you want it to look and behave, and Compose will take care of the rest. This greatly simplifies user interface development, as it allows you to focus on the visual and functional design of your application rather than worrying about the logic of interface construction.

Composable functions do not return anything; instead, they emit a UI.

Example of a Composable Function

Compose phases

Compose phases

When Compose updates a frame, it goes through three phases:

  • Composition: Composition in Compose refers to the process of building the user interface (UI). Compose decides what to display by executing composable functions and building the UI tree.
  • Layout: In this phase Compose determines the size and position of each element in the UI tree, ensuring everything fits correctly.
  • Drawing: Finally, Compose renders the individual UI elements on the screen, creating the final user interface that users see and interact with.

If you want to learn more about Compose phases, check out the following article: MAD Skills — Compose phases

Recomposition

Recomposition in Compose is the process where the user interface updates in response to changes in the application’s state. During recomposition, only components affected by the state change are re-evaluated, ensuring an efficiently updated UI.

A Composition can only be produced by an initial composition and updated by recomposition. The only way to modify a Composition is through recomposition.

Slot API

The Slot API in Compose is a technique that allows developers to create flexible and reusable UI components by exposing slots or placeholders where other composables can be inserted. This technique is especially useful for building UI components that need to be customized or extended in various ways without modifying the base component code. See more.

Take a look at this example:

@Composable
fun CustomCard(
headerContent: @Composable () -> Unit,
bodyContent: @Composable () -> Unit
) {
Column(
modifier = Modifier
.padding(16.dp)
.border(1.dp, Color.Black)
.padding(16.dp)
) {
// Slot for the header
headerContent()

Spacer(modifier = Modifier.height(8.dp))

// Slot for the body
bodyContent()
}
}
@Composable
fun MyScreen() {
CustomCard(
headerContent = {
Text(text = "Custom Header", style = MaterialTheme.typography.h6)
},
bodyContent = {
Text(text = "This is the body content of the card.", style = MaterialTheme.typography.body1)
}
)
}

State

State is any value that can change during the lifecycle of an application and affects how the user interface (UI) is represented. State allows the UI to respond to user actions. Without state, the UI would be static and could not react to events like button clicks, swipes, text inputs, and similar interactions.

Stateful

It refers to Stateful Composables that use remember to store data internally. This is useful when the caller does not need to control the state. However, Composables with internal state tend to be less reusable and more difficult to test.

@Composable
fun Greeting() {
// `name` is a state variable that holds the current value of the input field
var name by remember { mutableStateOf("") }
Column {
TextField(
value = name,
onValueChange = { newName -> name = newName },
label = { Text("Enter your name") }
)
Text(text = "Hello, $name!")
}
}

stateless

It refers to Composables that do not maintain any internal state. This is achieved through state hoisting. It is essential for situations where the state needs to be controlled externally, increasing reusability and making testing easier.

@Composable
fun Greeting(name: String, onNameChange: (String) -> Unit) {
Column {
TextField(
value = name,
onValueChange = onNameChange,
label = { Text("Enter your name") }
)
Text(text = "Hello, $name!")
}
}

State hoisting

It is a design pattern in Compose that involves moving the state from a composable to its caller to turn the composable into a stateless component. Instead of the composable maintaining its own internal state, the state is passed as a parameter from the composable’s caller. This facilitates the reusability of the composable in different contexts and promotes a more modular and decoupled architecture.

@Composable
fun StatefulGreeting() {
// `name` is a state variable that holds the current value of the input field
var name by remember { mutableStateOf("") }

// Using the stateless composable and passing the state and event handler
Greeting(name = name, onNameChange = { newName -> name = newName })
}

You can learn a little more in the following video: Where to hoist that state in Compose?

Modifiers

Modifiers are objects used to apply and configure changes to the appearance and behavior of user interface (UI) components. They are used to define aspects such as size, position, color, spacing, style, and more.

Side Effects

In Compose, side effects are operations that go beyond the scope of the composable functions’ UI rendering responsibilities. These include tasks such as making network requests, accessing databases, modifying shared preferences, or interacting with different parts of your Android application that are not directly related to the UI composition process. The challenge with side effects is ensuring they coexist harmoniously within Compose’s reactive and state-driven architecture.

For further exploration, you can click here to learn more.

CompositionLocal

Is a mechanism that allows you to provide and access data implicitly throughout the composition tree without passing it explicitly as parameters. Using CompositionLocalProvider, you can define a local context for values that can then be read in any descendant composition via the current property. This facilitates the access to shared data, such as themes or configurations, in a more flexible and decoupled manner.

We’ve covered the essential concepts of Compose in this article. Feel free to let me know in the comments what other fundamental Compose concepts you think were missing. Your feedback drives future content! 😊🚀

If you like my content and want to support my work, you can give me a cup of coffee ☕️ 🥰

Follow me in

--

--

Jorge Luis Castro Medina

I'm a Software Engineer passionate about mobile technologies, and I like everything related to software design and architecture