Jetpack Compose

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

Part 2: Build Complex UI with Compose

11. Build Custom Dialogs

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: 10. Add Actions & Handlers Next episode: 12. Add Themes & Styling to the App

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: 11. Build Custom Dialogs

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

Transcript: 11. Build Custom Dialogs

Demo

You can also build custom dialogs in compose! Let’s build one to be able to add reading lists. Create a new file within the readingLists.ui package, named ReadingListDialogs, and add the following code:

@Composable
fun AddReadingList(
  onAddList: (String) -> Unit = {},
  onDismiss: () -> Unit = {}
) {
  val inputState = remember { mutableStateOf("") }

  Dialog(onDismissRequest = onDismiss) {
    val shape = RoundedCornerShape(16.dp)
  }
}

Notice how you’re using the Dialog component. The dialog lets you define a dismiss request lambda, to trigger when you close the dialog.

You also define a content lambda, which describes the entire UI tree of the dialog, so it’s completely custom.

Now add the following code, to define a card with a text, a single input field and a button:

    Column(
      modifier = Modifier.background(
        MaterialTheme.colors.surface,
        shape = shape
      ).border(width = 1.dp, color = MaterialTheme.colors.primary, shape = shape),
      verticalArrangement = Arrangement.Center,
      horizontalAlignment = Alignment.CenterHorizontally
    ) {

    }

Now that you’ve defined a nice Column with a border and a material shape, you can add the UI controls.

      Spacer(modifier = Modifier.height(16.dp))

      Text(
        text = stringResource(id = R.string.add_reading_list_title),
        fontSize = 18.sp,
        fontWeight = FontWeight.Bold
      )

      InputField(
        label = stringResource(id = R.string.reading_list_name_hint),
        value = inputState.value,
        isInputValid = inputState.value.isNotEmpty(),
        onStateChanged = { newValue -> inputState.value = newValue }
      )

And finally, after adding the input field and the title, add the confirm button.

      ActionButton(
        modifier = Modifier.fillMaxWidth(),
        text = stringResource(id = R.string.add_reading_list_button_text),
        isEnabled = inputState.value.isNotEmpty(),
        onClick = { onAddList(inputState.value) },
      )

The code and the UI building process should be familiar to you by now. You have a single card, a text to describe what the dialog is about, and an input field for the name of the new reading list. And finally, you add a button to confirm the reading list name.

Now let’s fill in the ReadingListFragment, with the new action handlers. Open the ReadingListFragment file, and add the following code:

val readingListsState = mutableStateOf(emptyList<ReadingListsWithBooks>())

private val _isShowingAddReadingListState = mutableStateOf(false)

  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    loadReadingLists()
  }

  private fun loadReadingLists() {
    lifecycleScope.launch {
      readingListsState.value = repository.getReadingLists()
    }
  }

You add the state, and load the lists in onViewCreated().

Now add the Box to be able to display the dialog over the lists, and the logic that handles the dialog:

val readingLists = readingListsState.value

Box(
      modifier = Modifier.fillMaxSize(),
      contentAlignment = Alignment.Center
    ) {

    ReadingLists(readingLists, onItemClick = { onItemSelected(it) })
    
      val isShowingAddList = _isShowingAddReadingListState.value

      if (isShowingAddList) {
        AddReadingList(
          onDismiss = { _isShowingAddReadingListState.value = false },
          onAddList = { name ->
            addReadingList(name)
            _isShowingAddReadingListState.value = false
          })
      }
    }

Using the Box, you let the dialog draw over the list. Now finish off the dialog actions.

  @Composable
  fun AddReadingListButton() {
    FloatingActionButton(onClick = {
      _isShowingAddReadingListState.value = true
    }) {
      Icon(asset = Icons.Default.Add)
    }
  }


  fun addReadingList(readingListName: String) {
    lifecycleScope.launch {
      repository.addReadingList(ReadingList(name = readingListName, bookIds = emptyList()))

      readingListsState.value = repository.getReadingLists()
    }
  }

That’s it! You show the dialog only if the Boolean flag is true, and add the appropriate handles for the state. To do that, you use the Box again, and centered alignment. You also show the dialog you just defined, to let the user put in the reading list name.

Now build & run the app, and finally add some reading lists to your app.

[Build & run, add reading lists]

Awesome! In the next few episodes, you’ll learn how to reuse your code even more, and how to style your app, to make it even nicer! :]