User interfaces with Compose

Building Declarative Android UIs using Jetpack Compose and Kotlin

Updated: 03 September 2023

Jetpack Compose

Based on the Jetpack Compose Learning Path

Composable Functions

Using a Composable

Compose makes use of composable functions that let us define our UI programmatically

Composable functions make use of the@Composable annotation

We can render a composable Text element like so:

1
setContent {
2
Text("Hello World")
3
}

Compose uses the Kotlin compiler to turn these functions into UI elements

The Text element above will therefore render some text to the screen

Defining a Composable

We can define a composable function like so:

1
@Composable
2
fun Greeting(userName: String) {
3
Text(text = "Hello userName")
4
}

Composable Previews

Additionally, we can create a preview of a component using the @Preview annotation before the @Composable

So to preview our component above we can use the following:

1
@Preview
2
@Composable
3
fun GreetingPreview() {
4
Greeting("bob")
5
}

Additionally, we can include a background to our preview using the showBackground = true on the Preview annotation as can be seen below

1
@Preview(showBackground = true)
2
@Composable
3
fun GreetingPreview() {
4
Greeting("bob")
5
}

We can make use of Android Studio’s Design Preview to view Preview components

Composable Data

We can also make our component take more complex data, e.g. using a Data class like so:

1
data class User(val userName: String, val title: String)
2
3
@Composable
4
fun FancyGreeting(user: User) {
5
Text(user.displayName)
6
Text(user.title)
7
}
8
9
@Preview(showBackground = true)
10
@Composable
11
fun FancyGreetingPreview() {
12
FancyGreeting(user = User("bob", "The Business Man"))
13
}

Builtin Composables

As we’ve seen, there’s the Text composable component that’s defined by compose, in addition, we have a few other useful components that come pre-defined for us

First, there’s the Column component

1
import androidx.compose.foundation.layout.Column
2
3
// ...
4
5
Column {
6
Text("message")
7
Text("another message")
8
}

And the Row

1
import androidx.compose.foundation.layout.Row
2
3
// ...
4
5
Row {
6
Text("message")
7
Text("another message")
8
}

There’s also the Image

1
import androidx.compose.foundation.Image
2
import androidx.compose.ui.res.painterResource
3
4
// ...
5
6
Image(
7
painter = painterResource(R.drawable.profile_picture
8
contentDescription = "User profile picture"
9
)

And the Spacer

1
Spacer(modifier = Modifier.width(8.dp))

Modifiers

Modifiers allow us to change the styles of a specific component.

For example, we can add a modifier to a Column like so:

1
import androidx.compose.ui.Modifier
2
import androidx.compose.ui.unit.dp
3
4
// ...
5
@Composable
6
fun FancyGreeting(user: User) {
7
Column(modifier = Modifier.padding(all = 12.dp)) {
8
Text(user.userName)
9
Text(user.title)
10
}
11
}

We can further update the above example by adding some modifiers to the text:

1
@Composable
2
fun FancyGreeting(user: User) {
3
Column(modifier = Modifier.padding(all = 12.dp)) {
4
Text(
5
modifier = Modifier.padding(bottom = 8.dp),
6
text = user.userName
7
)
8
Text(user.title)
9
}
10
}

And, modifiers can also be chained to create more complex styles:

1
@Composable
2
fun FancyGreeting(user: User) {
3
Column(modifier = Modifier.padding(all = 12.dp)) {
4
Text(
5
modifier = Modifier
6
.padding(bottom = 8.dp)
7
.align(alignment = Alignment.CenterHorizontally),
8
text = user.userName
9
)
10
Text(user.title)
11
}
12
}

Text Styles

In addition to the above modifiers, the Text composable also takes a few additional properties that we can use to modify the style of the text. Here’s a simple way that we can add some styles to the Text element

1
Text(
2
text = user.userName,
3
modifier = Modifier
4
.padding(bottom = 8.dp)
5
.align(alignment = Alignment.CenterHorizontally),
6
style = TextStyle(
7
color = Color.Blue,
8
fontStyle = FontStyle.Italic
9
)
10
)

We can also define our app’s text styles in a central location and reuse it from there, or we can use the MaterialTheme ones as such:

1
Text(
2
text = user.title,
3
style = MaterialTheme.typography.body1
4
)

Applying this to our composable above:

1
@Composable
2
fun FancyGreeting(user: User) {
3
Column(modifier = Modifier.padding(all = 12.dp)) {
4
Text(
5
text = user.userName,
6
modifier = Modifier
7
.padding(bottom = 8.dp)
8
.align(alignment = Alignment.CenterHorizontally),
9
style = MaterialTheme.typography.h2
10
)
11
Text(
12
text = user.title,
13
style = MaterialTheme.typography.body1
14
)
15
}
16
}

Lists

List views can be done using the LazyRow and LazyRow composable. These composables have an items lambda which can be used to render a given item

Note that we can use the sample data from androidx.compose.foundation.lazy.items

1
@Composable
2
fun MessageList(messages: List<Message>) {
3
LazyColumn {
4
items(messages) { message ->
5
FancyGreeting(
6
User(message.name, message.message)
7
)
8
}
9
}
10
}

State Management

Composable functions can store local state by using remember, and can track changes to the value passed to mutableStateOf. State will then be redrawn automatically when the value is updated

Using this method of state management we use the by keyword. This keyword delegates the getter and setter value for a specific variable to a function (in this case, remember)

Using the above, we can create an ExpandableGreeting which manages the state required by our FancyGreeting

1
@Composable
2
fun ExpandableGreeting (user: User) {
3
var expanded by remember {
4
mutableStateOf(false)
5
}
6
7
val onExpand = {
8
expanded = !expanded
9
}
10
11
FancyGreeting(user, expanded, onExpand)
12
}

And we can also update our FancyGreeting to take some expand-related options

1
@Composable
2
fun FancyGreeting(user: User, expanded: Boolean, onExpand: () -> Unit) {
3
Column(
4
// ...
5
modifiers = Modifier.clickable {
6
onExpand()
7
}
8
) {
9
Text(
10
// ...
11
maxLines = if (expanded) Int.MAX_VALUE else 1,
12
)
13
}
14
}

Animations

Compose provides us with a modifier for handling animation that we can apply to the Text above, you can see this below:

1
Text(
2
maxLines = if (expanded) Int.MAX_VALUE else 1,
3
modifier = Modifier.animateContentSize()
4
)

This will make it automatically animate when expanded