Input Dialogs with Material Components

I've always been a fan of afollestad/material-dialogs, as it makes it really easy to create good looking dialog popups for my Android apps, and the module system it offers is great for input dialogs. It's always been my go-to for a pop-up dialog.

That being said, I'm currently working on project in which I'm trying to minimise the number of third party libraries involved - hopefully there'll be less cases of libraries no longer being actively supported. (Although I doubt this will happen to the highly depended on material-dialogs)

The Material Components for Android are hugely better than the design libraries that Android developers have had in the past, and there's now a solid source of truth for how applications should be built. Of course, the library has it's issues (it seems to be eternally in a beta state as of time of writing) but it's so powerful for creating themed Android applications that don't look like the rest.

So, I was using material-dialogs for a simple dialog like this:
MaterialDialog(requireContext()).show {
    title(R.string.dialog_title)
    message(R.string.dialog_message)
    positiveButton(R.string.dialog_positive) {
        viewModel.performAction()
    }
    negativeButton(R.string.dialog_negative)
}
Material Components has a very similar syntax to create an identical popup - it looks like this:
MaterialAlertDialogBuilder(requireContext())
    .setTitle(R.string.dialog_title)
    .setMessage(R.string.dialog_message)
    .setPositiveButton(R.string.dialog_positive) { _, _ ->
        viewModel. performAction()
    }.setNegativeButton(R.string.dialog_negative, null)
    .show()
Great! The resulting popup looks very similar when using a MaterialComponents theme, and unlike material-dialogs (right now) the dialog adopts the shape theme set in your application's theme.



Easy, and the dialog looks great. So what problems are we going to run into?

Well, since input dialogs aren't in the Material Design spec, Material Components don't include an easy way of creating an input dialog like this from afollestad/material-dialogs):


Therefore we're going to have to create our own custom view for the contents of our dialog. I've bundled my solution into a Kotlin Extension function that you can stick in a file and hopefully use without having to change much. Here is the end result. I'll leave the code below and then explain extra details that might help if you run into problems.


Kotlin Extensions

This uses AndroidX libraries including KTX.
import android.app.Dialog
import android.content.DialogInterface
import android.view.LayoutInflater
import androidx.appcompat.app.AlertDialog
import androidx.core.widget.doOnTextChanged
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.textfield.TextInputLayout

fun MaterialAlertDialogBuilder.showInput(
    layout: Int,
    tilId: Int,
    hintRes: Int,
    counterMaxLength: Int = 0,
    prefilled: String = ""
): Dialog {
    this.setView(layout)
    val dialog = this.show()
    val til = dialog.findViewById<TextInputLayout>(tilId)
    til?.let {
        til.hint = context.getString(hintRes)
        if (counterMaxLength > 0) {
            til.counterMaxLength = counterMaxLength
            til.isCounterEnabled = true
        }
        til.editText?.doOnTextChanged { text, start, before, count ->
            dialog.getButton(AlertDialog.BUTTON_POSITIVE)
                .isEnabled = !text.isNullOrBlank() && (counterMaxLength == 0 || text.length <= counterMaxLength)
        }
        til.editText?.append(prefilled)
        dialog.getButton(AlertDialog.BUTTON_POSITIVE)
            .isEnabled = !prefilled.isBlank()
    }
    return dialog
}

fun DialogInterface.inputText(tilId: Int): String {
    return (this as AlertDialog).findViewById<TextInputLayout>(tilId)?.editText?.text?.toString().orEmpty()
}
Now to use this, you need to create a layout file with the TextInputLayout that you would like displayed, including any padding - for example dialog_text_input.xml

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingTop="8dp"
    android:paddingStart="?attr/dialogPreferredPadding"
    android:paddingEnd="?attr/dialogPreferredPadding">

  <com.google.android.material.textfield.TextInputLayout
      android:id="@+id/dialog_text_input_layout"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_margin="4dp">

    <com.google.android.material.textfield.TextInputEditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
  </com.google.android.material.textfield.TextInputLayout>
</FrameLayout>
Here I've set a 4dp margin to the TextInputLayout and have given the TextInputLayout and id that I can refer to later.

When I want to use the new input dialog instead of calling show(), I will call our new extension function, showInput.

MaterialAlertDialogBuilder(requireContext())
    .setTitle(R.string.dialog_title)
    .setPositiveButton(R.string.dialog_positive) { dialog, _ ->
        viewModel.performAction(dialog.inputText(tilId =  R.id.dialog_text_input_layout))
    }.setNegativeButton(R.string.dialog_negative, null)
    .showInput(layout= R.layout.dialog_text_input,
        tilId = R.id.dialog_text_input_layout,
        hintRes= R.string.dialog_hint,
        counterMaxLength = 15,
        prefilled = viewModel.energySensor.name.orEmpty())
Note the different parameters we are passing into showInput - we need the layout file and TextInputLayout, as well as hint, counter and prefill information. This can obviously be changed to fit your use by changing the extension function. The button will be disabled if no text is entered, and in the callback, you can use the .inputText extension function to get the text from the enclosed EditText view. Perfect!


In this article I took inspiration from this blog post that suggested showing the dialog before updating the TextInputLayout

Comments