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.

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

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 a Recipe 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 a Recipe 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.