Jetpack Compose

Oct 11 2022 · Kotlin 1.7.10, Android 13, Android Studio Chipmunk

Part 2: Build Complex UI with Compose

10. Add Actions & Handlers

Episode complete

Play next episode

Next
About this episode

Leave a rating/review

See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 09. Apply Error & Data Handling to the UI Next episode: 11. Build Custom Dialogs

Get immediate access to this and 4,000+ other videos and books.

Take your career further with a Kodeco Personal Plan. With unlimited access to over 40+ books and 4,000+ professional videos in a single subscription, it's simply the best investment you can make in your development career.

Learn more Already a subscriber? Sign in.

Notes: 10. Add Actions & Handlers

The student materials have been reviewed and are updated as of September 2022.

Transcript: 10. Add Actions & Handlers

Intro

[Slide 1 - Dialogs and Actions]

You’ve added quite a lot of UI to your app! :] You can now add Books and their reviews, to fill in data for your library.

But you should also be able to delete books and reviews, in case you want to change your opinion, or you no longer have the books in your library!

And to do that, it’s best to use Dialogs and long click actions in your lists. You can build both of these very easily in Jetpack Compose! Let’s see how! :]

[Switch to demo]

Demo

Let’s start off with the BookReviews feature. Open the BookReviewsFragment, and add the following code at the top:

private val _deleteReviewState = mutableStateOf<BookReview?>(null)

You might be wondering why you need to store the book review you want to delete. Well everything in Compose is powered by state, which you change to display or hide different parts of the UI.

This is why you store the review to delete. You start with a null value, which will represent the state where you don’t have anything to delete.

Then once this value changes to a proper review, you’ll show a dialog, with that review to delete, in the positive dialog action!

Let’s implement that bit now.

Add the following code to BookReviewContentWrapper:

Box(
  contentAlignment = Alignment.Center,
  modifier = Modifier.fillMaxSize()
) {
    
    val reviewToDelete = _deleteReviewState.value

      if (reviewToDelete != null) {
        DeleteReviewDialog(
          item = reviewToDelete,
          message = stringResource(id = R.string.delete_review_message, reviewToDelete.book.name),
			...
        )
      }

You just added the basic Dialog data and parameters, as well as a Box component to store your state and the dialog. Now add the dialog actions and the deleteReview() function code.

...
          onDeleteItem = { bookReview ->
            deleteReview(bookReview)
            _deleteReviewState.value = null
          },
          onDismiss = {
            _deleteReviewState.value = null
          }

...   
  fun deleteReview(bookReview: BookReview) {
    lifecycleScope.launch {
      repository.removeReview(bookReview.review)

      bookReviewsState.value = repository.getReviews()
    }
  }

Notice the Box and the if check. The Box is a special component that lets you stack items on top of each other. You also added the center align modifiers, to put the dialog right in the center of the screen.

Then you added the if check, to see if there is a review to delete and show the dialog for. You also show the dialog within the if check. Now this is the important thing. Because compose won’t run the function unless there is a value in the state, you won’t show the dialog at all times.

You’ll only show it once you change the value of the state, just like you did before with Compose.

Now let’s change the way the BookReviewsList works, to set up the way to change that state value.

BookReviewsList in BookReviewsContentWrapper:

onItemLongTap = {
  _deleteReviewState.value = it
}

BookReviewsList:

onItemLongTap: (BookReview) -> Unit

LazyColumn(Modifier.fillMaxSize()) // new

// BookReviewItem
.combinedClickable(
  interactionSource = remember { MutableInteractionSource() },
  onClick = { onItemClick(bookReview) },
        onLongClick = { onItemLongTap(bookReview) },
        indication = null)

That’s all you need to do to add a long click listener, and the state handling! :]

Also make sure to add the experimental API annotation, as the combinedClickable modifier is still experimental.

As soon as you long tap on an item, you’ll update the state, and the dialog will show! Now let’s add the delete dialog for the reviews!

Create a new file called DeleteDialog in the composeUi package, and add the following starting code:

@Composable
fun DeleteReviewDialog(
  item: BookReview,
  message: String,
  onDeleteItem: (BookReview) -> Unit,
  onDismiss: () -> Unit
) {

}

You need four parameters and pieces of information. The item you want to delete, the message for the dialog, and two actions - the delete action and the dismiss action.

Now fill in the dialog code:

  AlertDialog(
    title = { Text(text = stringResource(id = R.string.delete_title)) },
    text = { Text(text = message) },
    onDismissRequest = onDismiss,
    buttons = {
      Row(
        modifier = Modifier.fillMaxWidth(),
        horizontalArrangement = Arrangement.End
      ) {

      }
    })

After adding the AlertDialog and its fundamental properties such as the title and text, you defined a buttons composable. Let’s fill in that composable function.

        DialogButton(
          text = R.string.yes,
          onClickAction = { onDeleteItem(item) }
        )

        DialogButton(text = R.string.cancel, onClickAction = onDismiss)

Just like with the regular Android Toolkit, you can use an AlertDialog. The dialog needs a title, a text or message, a dismiss request lambda and a composable function which describes the buttons.

The buttons you’ve built will be in a Row. One button will represent the delete action and the other will represent the cancel action.

Because you are missing the DialogButton composable, let’s create it. Create a new file named DialogButton, and add the following code:

@Composable
fun DialogButton(
  modifier: Modifier = Modifier,
  @StringRes text: Int,
  onClickAction: () -> Unit
) {
  TextButton(
    modifier = modifier.padding(start = 8.dp, end = 8.dp, top = 8.dp, bottom = 8.dp),
    colors = buttonColors(
      backgroundColor = colorResource(id = R.color.colorPrimary),
      contentColor = Color.White),
    onClick = onClickAction
  ) {
    Text(text = stringResource(id = text))
  }
}

It’s pretty straightforward in what it does. It’s a simple TextButton with extra padding, and a regular click action.

Now build & run the app after sorting out the imports, and try to delete a review!

[Build & Run]

You can now delete reviews! :] Well done!

If you skip over to the final project for this episode, you’ll find that the same feature was built for Books. That way you can find another example of using dialogs to delete items.

If you build & run the final project, you should be able to delete books too! :]


That’s awesome! All of your features are slowly coming together! :]

In the next episode, you’ll learn about custom dialogs! See you there!