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
Post a Comment