Jetpack Compose Tutorial for Android: Getting Started
In this Jetpack Compose tutorial, you’ll learn to use the new declarative UI framework being developed by the Android team by creating a cookbook app. By Joey deVilla.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Jetpack Compose Tutorial for Android: Getting Started
50 mins
- What Is Jetpack Compose?
- Your First Jetpack Compose App
- Hello, Android!
- A Quick Aside: What are Composable Functions?
- Back to Your First Jetpack Compose App
- Previewing a Composable
- Composable Parameters
- Your First Custom Composable Functions
- Laying Out Composables
- Introducing Compose Cookbook
- Creating a Recipe Class and Recipe Instances
- Creating a Recipe Card Composable
- Adding an Image to the Recipe Card
- Listing Ingredients
- Adding the Description
- Improving the Recipe Card’s Typography
- Adding Space to the Recipe Card
- Rounding the Recipe Card’s Corners
- Displaying the List of Recipes
- Wiring Everything Up
- Adding a Toolbar
- Making the App a Little More “Real World”
- Add a ViewModel
- Adding Methods to Change the Recipe List
- Adding UI Testing
- Where to Go From Here?
Listing Ingredients
Each Recipe
instance has a property called ingredients
, which is a list of strings. You can display the ingredients by adding the following below the Text
for the recipe’s title:
for (ingredient in recipe.ingredients) {
Text(ingredient)
}
One of the awesome elements of Jetpack Compose is you can use ordinary Kotlin code to express slightly more complex UI details. In the code above, you use a for
loop to list each item in the recipe’s ingredients
property using a Text()
composable. If you rebuild the UI you’ll see all the ingredients of this delicious ramen meal listed below the title. Not only is that cool, but you were also spared from having to define a RecyclerView.Adapter
or any ViewHolders
.
Refresh the preview. You should see this:
You’ll need one more piece of information to add to the card: the description.
Adding the Description
The right side of the recipe card below the photo has plenty of space, making it a good place to put the description. To do this, you’ll need another layout composable: Column
’s counterpart, Row
. Like Column
, Row
acts like a LinearLayout
, but with its orientation set to horizontal
.
It’s often helpful to work from a diagram as your Jetpack Compose layouts get more complex. Here’s a diagram showing how you’ll use Column
and Row
layout composables to arrange the UI elements on the recipe card:
You can see the body of the recipe card is still contained inside a Column
, but now there’s a Row
beneath the photo. The Row
contains two elements: A Column
containing the recipe title and the list of ingredients on the left, and the description on the right.
Implement this new layout by updating RecipeCard()
as shown below:
@Composable
fun RecipeCard(recipe: Recipe) {
Column(modifier = Modifier.fillMaxWidth()) {
Image(
painterResource(recipe.imageResource),
contentDescription = recipe.title,
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxWidth().height(144.dp)
)
Row {
Column {
Text(recipe.title)
for (ingredient in recipe.ingredients) {
Text(ingredient)
}
}.
Text(recipe.description)
}
}
}
The preview should show a complete — if not exactly pretty — recipe card:
Now that the recipe card is showing the all the recipe information, it’s time to improve its look.
Improving the Recipe Card’s Typography
The recipe card’s title should be headline-sized and in bold text, and the list of ingredients should have bullet points to make it list-like. Update the Row
in RecipeCard
to the following:
Row {
Column {
Text(
text = recipe.title,
style = MaterialTheme.typography.headlineLarge,
fontWeight = FontWeight(700)
)
for (ingredient in recipe.ingredients) {
Text(
text = "• $ingredient",
style = MaterialTheme.typography.bodyMedium
)
}
}
Text(
text = recipe.description,
style = MaterialTheme.typography.bodySmall
)
}
You added some Text()
parameters:
-
style
, which sets theText
composable output’s font face, weight and size to those defined by the current UI theme’s styles. You used it to set the recipe title to the large headline style, the ingredients to the medium body text style, and the description, which can get long, to the small body text style. -
fontWeight
, which you used to make the recipe title a little more bold.
You also changed the Text()
call for each recipe ingredient so it starts with a bullet point (•).
This is an improvement, but the card still looks crowded. Fix it by adding space around the text elements.
Adding Space to the Recipe Card
The card needs a little breathing room. You already know how to add padding to a composable by using its modifier
parameter, so do that by adding a modifier at the start of the Column()
composable containing the title and ingredients …
Column (modifier = Modifier.padding(16.dp)) {
...
…and update the Text()
composable for the description with the same modifier:
Text(
text = recipe.description,
style = MaterialTheme.typography.bodySmall,
modifier = Modifier.padding(16.dp)
)
The preview should show you a recipe card that looks less cluttered:
The placement of the description doesn’t look quite right. It should start at the same y-coordinate as the ingredients, not the title. This can be fixed with a Spacer
, a composable that emits an empty UI element whose only purpose is to add space between UI elements. A Spacer
that’s the same height as the title, placed above the description, would fix this problem.
Add this line before the Text()
composable for the description:
Spacer(modifier = Modifier.height(42.dp))
You should see this in the preview:
The description should have been pushed further down the card, but it doesn’t look as though anything changed. What happened?
The Spacer
is there, but it’s to the left of the description, not above it. Remember — the Text
for the description is inside a Row
, which lays out its elements from left to right. The Spacer
has a specified height, but no specified width, which the Android layout engine interprets as “make it as thin as possible so it will fit.”
The way to fix this is to put both the Spacer
and the description Text
inside a Column
. Replace the Spacer
and description Text
with this code:
Column(modifier = Modifier.padding(16.dp)) {
Spacer(modifier = Modifier.height(42.dp))
Text(
text = recipe.description,
style = MaterialTheme.typography.bodySmall
)
}
That’s much better.
Rounding the Recipe Card’s Corners
Rectangles with rounded corners aren’t just a design trend. There’s research showing we prefer rounded objects over ones with sharp edges, and many UI/UX designers agree withthose findings. I also think that they make for nicer-looking content containers.
The simplest (and preferred) way to add rounded corners to a composable is to compose it into a Surface
composable, which is particularly good at applying color, shape and elevation to the UI element contained by it. Update the RecipeCard
function as shown below:
@Composable
fun RecipeCard(recipe: Recipe) {
Surface(
color = MaterialTheme.colorScheme.surface,
border = BorderStroke(1.dp, MaterialTheme.colorScheme.secondary),
shape = RoundedCornerShape(8.dp),
tonalElevation = 2.dp,
shadowElevation = 10.dp
) {
Column(modifier = Modifier.fillMaxWidth()) {
Image(
painterResource(recipe.imageResource),
contentDescription = recipe.title,
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxWidth().height(144.dp)
)
Row {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = recipe.title,
style = MaterialTheme.typography.headlineLarge,
fontWeight = FontWeight(700)
)
for (ingredient in recipe.ingredients) {
Text(
text = "• $ingredient",
style = MaterialTheme.typography.bodyMedium
)
}
}
Column(modifier = Modifier.padding(16.dp)) {
Spacer(modifier = Modifier.height(42.dp))
Text(
text = recipe.description,
style = MaterialTheme.typography.bodySmall
)
}
}
}
}
}
The Surface
parameters worth noting are:
-
shape
: You used this to specify that the card should have corners rounded with a 12 dp radius. -
tonalElevation
: This is the first of two parameters that provide the illusion of depth. A higher value means the UI element is closer to the user. Elements closer to the user are colored darker in light theme and lighter in dark theme. -
shadowElevation
: The second of two parameters that provide the illusion of depth, this sets the size of the shadow below the UI element.
You now have a nice-looking recipe card: