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 Alex Sullivan.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Introducing ComposableCookBook

That was quite an introduction, and I can only imagine how excited and all over the place you are! But you might want to compose yourself because you’ll be working on an even cooler feature for the project! :]

Instead of building simple red texts, you’re going to build a cookbook app called ComposableCookBook that displays a list of delicious recipes. The project comes with a pre-defined Recipe:

data class Recipe(
  @DrawableRes val imageResource: Int,
  val title: String,
  val ingredients: List<String>
)

A Recipe has an image, a title and a list of ingredients. The app also ships with a default list of recipes in Recipes.kt. You’ll use this list of recipes to build your cookbook app.

Creating a Recipe Composable

Your goal is to use Jetpack Compose to make the ComposeCookbook recipe app look better, by creating a UI that shows each recipe in a card with the recipe’s image at the top of the card and a list of ingredients below it.

Your first step is to create a composable that shows an individual recipe.

Right-click on the ComposableCookBook package and select New ▸ New Kotlin File/Class. Then select File from the list and type the name RecipeCard. Lastly, add the composable method, RecipeCard(), shown below:

@Composable
fun RecipeCard(recipe: Recipe) {
  Text(recipe.title)
}

For now, this just displays the title of the recipe using Text().

Since RecipeCard() takes a parameter, you can’t use @Preview. However, you can create another composable method that supplies a default RecipeCard(). Add the following below RecipeCard():

@Composable
@Preview
fun DefaultRecipeCard() {
  RecipeCard(defaultRecipes[0])
}

Build and run. You can now preview the RecipeCard().

Preview of Recipe Card

Next, you’re going to add an image to RecipeCard(), above the title.

Adding an Image to the RecipeCard

Replace the contents of RecipeCard() with the following:

// 1
val image = +imageResource(recipe.imageResource)
Column {
  // 2
  Container(expanded = true, height = 144.dp) {
    // 3
    DrawImage(image = image)
  }
  Text(recipe.title)
}

Make sure to import all the functions which might be marked as red, as unresolved references. There’s a lot of magic going on there for such a tiny chunk of code! Here’s a breakdown:

In Jetpack Compose, you use a different mechanism to get an instance of the Image. This line tells Jetpack Compose to fetch an Image from a given drawable resource identifier.

Pay special attention to the .dp method called on the height. Compose exposes an extremely handy extension method to turn numbers into dps!

  1. Each Recipe comes with an image resource which points to a Drawable. Normally, you’d either set the drawable on an ImageView or use a Context to get an instance of a Drawable.
  2. Next, you need some way to size the image you’ll show. Container is a simple layout composable that can help you size a child composable. In this scenario, you’re telling it to fully expand horizontally and to have a height of 144 dp.
  3. Finally, you’re using DrawImage() to render the image, passing through the Image you retrieved in the first step.

Refresh the preview and you’ll see your recipe card starting to take shape!


Recipe card with image preview

That ramen looks pretty tasty – but what ingredients do you need to cook it? For your next task, you’ll create an ingredient list.

Listing Ingredients

To list the ingredients, you’ll use your handy Text(). Since you already defined a Column() in the previous step, adding the ingredients will be easy.

Add the following below the Text drawing the recipe’s title:

for (ingredient in recipe.ingredients) {
  Text(ingredient)
}

One of the awesome things about Jetpack Compose is that you can use normal Kotlin code to express slightly more complex UI details.

In the above code, you use a for loop to list all the Text ingredients. If you rebuild the UI you’ll see all the ingredients of this delicious ramen meal listed below the title. Pretty cool, right? And you didn’t have to define a RecyclerView.Adapter, or ViewHolders!

List of recipe ingredients preview

That ramen does look tasty, but the recipe card itself looks pretty square. Next, you’ll add rounded corners to the recipe card, to make it nicer.

Rounding the RecipeCard’s Corners

Using a Surface() parent gives you the option to round the corners of your item. Replace the existing contents of RecipeCard() with the following:

Surface(shape = RoundedCornerShape(8.dp), elevation = 8.dp) {
  val image = +imageResource(recipe.imageResource)
  Column {
    Container(expanded = true, height = 144.dp) {
      DrawImage(image = image)
    }
    Text(recipe.title)
    for (ingredient in recipe.ingredients) {
      Text("• $ingredient")
    }
  }
}

Surface() handles drawing shapes and providing a component’s elevation. For the recipe card, you’ll use a Surface() with a RoundedCornerShape() and an elevation of 8 dp.

Build and run to update the preview and you’ll notice that nothing happens. You’ll also see a small exclamation point icon in the top-right of the preview window, which indicates an error or a warning.


Error drawing the Surface preview

Click on that icon and you’ll see the error message. It might say that the view cannot have a width of zero (0), or that no colors are found to draw the UI. At the moment of writing the tutorial, we received different errors on different machines, so you might get something different. No matter what, this error message doesn’t explicitly indicate the problem – a drawback of Jetpack Compose still being in the pre-alpha stage.

Nevertheless, the problem is that Jetpack Compose cannot draw the UI, do to the lack of a color value, but that lack comes from the lack of a Theme, so that’s where the culprit lies.

Adding a Theme

To find the origin of the real problem, Command-Click on Mac or Control-click in Windows on Surface(). You’ll see something like this:

@Composable
fun Surface(
    shape: Shape = RectangleShape,
    color: Color = +themeColor { surface },
    border: Border? = null,
    elevation: Dp = 0.dp,
    children: @Composable() () -> Unit
)

Notice the line defining a color:

color: Color = +themeColor { surface }

Compose uses a theming engine that lets you swap out different themes as your UI dictates. However, you need to make sure you have a theme composable above any composable that tries to fetch a theme color.

In this scenario, the preview fails to show because there’s no theme composable higher up in the chain telling this Surface() what the surface color should be.

Fix the problem by wrapping the RecipeCard() with a MaterialTheme() in DefaultRecipeCard():

@Composable
@Preview
fun DefaultRecipeCard() {
  MaterialTheme {
    RecipeCard(defaultRecipes[0])
  }
}

MaterialTheme()/code> is a built-in theme that offers many colors and text styles out of the box.

Refresh the build. The preview should now show a rounded card!

Rounded RecipeCard preview

The card is starting to shape up, but it's missing two things: some basic styling on the title component and some padding between elements. You'll take care of that in the next step.