User interfaces with Compose
Building Declarative Android UIs using Jetpack Compose and Kotlin
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:
setContent {
Text("Hello World")
}
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:
@Composable
fun Greeting(userName: String) {
Text(text = "Hello userName")
}
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:
@Preview
@Composable
fun GreetingPreview() {
Greeting("bob")
}
Additionally, we can include a background to our preview using the showBackground = true
on the Preview
annotation as can be seen below
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
Greeting("bob")
}
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:
data class User(val userName: String, val title: String)
@Composable
fun FancyGreeting(user: User) {
Text(user.displayName)
Text(user.title)
}
@Preview(showBackground = true)
@Composable
fun FancyGreetingPreview() {
FancyGreeting(user = User("bob", "The Business Man"))
}
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
import androidx.compose.foundation.layout.Column
// ...
Column {
Text("message")
Text("another message")
}
And the Row
import androidx.compose.foundation.layout.Row
// ...
Row {
Text("message")
Text("another message")
}
There's also the Image
import androidx.compose.foundation.Image
import androidx.compose.ui.res.painterResource
// ...
Image(
painter = painterResource(R.drawable.profile_picture
contentDescription = "User profile picture"
)
And the Spacer
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:
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
// ...
@Composable
fun FancyGreeting(user: User) {
Column(modifier = Modifier.padding(all = 12.dp)) {
Text(user.userName)
Text(user.title)
}
}
We can further update the above example by adding some modifiers to the text:
@Composable
fun FancyGreeting(user: User) {
Column(modifier = Modifier.padding(all = 12.dp)) {
Text(
modifier = Modifier.padding(bottom = 8.dp),
text = user.userName
)
Text(user.title)
}
}
And, modifiers can also be chained to create more complex styles:
@Composable
fun FancyGreeting(user: User) {
Column(modifier = Modifier.padding(all = 12.dp)) {
Text(
modifier = Modifier
.padding(bottom = 8.dp)
.align(alignment = Alignment.CenterHorizontally),
text = user.userName
)
Text(user.title)
}
}
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
Text(
text = user.userName,
modifier = Modifier
.padding(bottom = 8.dp)
.align(alignment = Alignment.CenterHorizontally),
style = TextStyle(
color = Color.Blue,
fontStyle = FontStyle.Italic
)
)
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:
Text(
text = user.title,
style = MaterialTheme.typography.body1
)
Applying this to our composable above:
@Composable
fun FancyGreeting(user: User) {
Column(modifier = Modifier.padding(all = 12.dp)) {
Text(
text = user.userName,
modifier = Modifier
.padding(bottom = 8.dp)
.align(alignment = Alignment.CenterHorizontally),
style = MaterialTheme.typography.h2
)
Text(
text = user.title,
style = MaterialTheme.typography.body1
)
}
}
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
@Composable
fun MessageList(messages: List<Message>) {
LazyColumn {
items(messages) { message ->
FancyGreeting(
User(message.name, message.message)
)
}
}
}
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
@Composable
fun ExpandableGreeting (user: User) {
var expanded by remember {
mutableStateOf(false)
}
val onExpand = {
expanded = !expanded
}
FancyGreeting(user, expanded, onExpand)
}
And we can also update our FancyGreeting
to take some expand-related options
@Composable
fun FancyGreeting(user: User, expanded: Boolean, onExpand: () -> Unit) {
Column(
// ...
modifiers = Modifier.clickable {
onExpand()
}
) {
Text(
// ...
maxLines = if (expanded) Int.MAX_VALUE else 1,
)
}
}
Animations
Compose provides us with a modifier for handling animation that we can apply to the Text
above, you can see this below:
Text(
maxLines = if (expanded) Int.MAX_VALUE else 1,
modifier = Modifier.animateContentSize()
)
This will make it automatically animate when expanded