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?
Adding Methods to Change the Recipe List
Adding state to the app isn’t interesting until there’s a way to change that state. To keep things simple, here are two methods you should add to RecipeListViewModel
, just below the declarations for _recipeListFlow
and recipeListFlow
:
fun addPlaceholderRecipe() {
recipeList.add(
Recipe(
imageResource = R.drawable.fork_spoon,
title = "Placeholder",
ingredients = listOf("Ingredient 1", "Ingredient 2", "Ingredient 3"),
description = "Lorem ipsum yummy yum!"
)
)
}
fun removeLastRecipe() {
if (recipeList.isNotEmpty()) {
recipeList.remove(recipeList.last())
}
}
Here’s a quick rundown of what these methods do:
-
addPlaceholderRecipe()
: Creates aRecipe
object with placeholder information (including the one food photo that the app hasn’t yet used) and adds it to the recipe list. -
removeLastRecipe()
: Deletes the last recipe from the list (and only if the list contains at least one recipe).
To call these methods, add a Row
containing two Button()
composables to the Cookbook()
composable, nestled between the TopAppBar
and the list:
-
addPlaceholderRecipe()
: Creates aRecipe
object with placeholder information (including the one food photo that the app hasn’t yet used) and adds it to the recipe list. -
removeLastRecipe()
: Deletes the last recipe from the list (and only if the list contains at least one recipe).
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Cookbook(viewModel: RecipeListViewModel) {
Column(modifier = Modifier.fillMaxSize()) {
TopAppBar(
title = {
Text(
text = "Compose Cookbook",
modifier = Modifier.testTag("topAppBarText")
)
},
colors = TopAppBarDefaults.smallTopAppBarColors(containerColor = Color.LightGray),
modifier = Modifier.testTag("topAppBar")
)
Row(
horizontalArrangement = Arrangement.Center,
modifier = Modifier.padding(16.dp)) {
Button(
onClick = { viewModel.addPlaceholderRecipe() },
modifier = Modifier
.padding(8.dp)
.testTag("addPlaceholderButton")
) {
Text(text = "Add Placeholder")
}
Button(
onClick = { viewModel.removeLastRecipe() },
modifier = Modifier
.padding(8.dp)
.testTag("removeLastButton")
) {
Text(text = "Remove Last")
}
}
Row {
RecipeList(viewModel)
}
}
}
Button()
composables take a parameter called onClick
, in which you can define what action should be taken when the button is pressed. The first button will call the ViewModel’s addPlaceholderRecipe()
method, and the second will call the ViewModel’s removeLastRecipe()
method.
Build and run the app, and try pressing the Add Placeholder and Remove Last buttons, and see how the cookbook changes.
Adding UI Testing
Finally, you should perform some UI tests. These are useful for determining that objects that should be onscreen are actually onscreen (and vice versa), as well as confirming that UI actions such as button presses are producing the desired result. In this step, you’ll create some tests to confirm that the Cookbook
composable is working properly.
The first step is to create a test class. UI tests are instrumented tests, which go into their own group, separate from the app. In the group marked (androidTest), create a class called CookbookTest and add the following code to it:
import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.assertTextContains import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.performClick import org.junit.Before import org.junit.Rule import org.junit.Test class CookbookTest { private val viewModel = RecipeListViewModel() @get:Rule(order = 0) val composeTestRule = createComposeRule() @Before fun before() { composeTestRule.setContent { Cookbook(viewModel) } } @Test fun topAppBarShouldBeVisibleAndShowCorrectTitle() { composeTestRule.onNodeWithTag("topAppBar").assertIsDisplayed() composeTestRule.onNodeWithTag("topAppBarText").assertTextContains("Compose Cookbook") } @Test fun whenAddPlaceHolderButtonClicked_recipeCountShouldIncreaseBy1() { val oldRecipeCount = viewModel.recipeListFlow.value.count() composeTestRule.onNodeWithTag("addPlaceholderButton").performClick() val newRecipeCount = viewModel.recipeListFlow.value.count() assert(newRecipeCount - oldRecipeCount == 1) } @Test fun whenRemoveLastButtonClicked_recipeCountShouldDecreaseBy1() { val oldRecipeCount = viewModel.recipeListFlow.value.count() composeTestRule.onNodeWithTag("removeLastButton").performClick() val newRecipeCount = viewModel.recipeListFlow.value.count() assert(oldRecipeCount - newRecipeCount == 1) } }
The class provides three tests, whose names are meant to be self-explanatory. topAppBarShouldBeVisibleAndShowCorrectTitle()
is one of those tests that checks whether UI elements are being drawn, while whenAddPlaceHolderButtonClicked_recipeCountShouldIncreaseBy1()
and whenRemoveLastButtonClicked_recipeCountShouldDecreaseBy1()
simulate presses on the Add Placeholder and Remove Last buttons and confirm the recipe list changes accordingly.
The simplest way to run each of the tests in this class is to click the green play (▶️) button to the left of each test function’s name. Android Studio will take a moment or two to run the test, and all of them should pass.
Where to Go From Here?
You’ve done a lot of work, and you’ve just scratched the surface of building Android UIs the new way. There’s still a lot of Jetpack Compose ground to cover, and you might want to check out out our video course on this topic, as well as our book Jetpack Compose by Tutorials.
You can also see some of the latest and greatest elements in action by browsing the JetNews sample app, which some of the authors of Jetpack Compose develop and maintain.
We hope you’ve enjoyed this tutorial. Questions or comments? Feel free to join the forum discussion below.