How to show success state on TextInputLayout (similar to setError())

31 Views Asked by At

I need to show a success state on Material's TextInputLayout. I just need the same UI as this layout shows when an error is set. Following is what I need:

class CustomTextInputLayout(
    context: Context,
    attributes: AttributeSet?,
): TextInputLayout(context, attributes) {
    fun setSuccessMode(successText: CharSequence?)
    fun setErrorMode(errorText: CharSequence?)
    fun setHelperTextMode(helperText: CharSequence?)
}

I am not sure how to do this using themes and styles. I planned to trick TextInputLayout to show different colours on the basis of a variable, but still use setError() functionality. But, I don't think I can change theme for TextInputLayout in runtime easily. Also, introducing a variable will require me to preserve this over savedState changes.

1

There are 1 best solutions below

0
rupinderjeet On BEST ANSWER

This is what I ended up using:

/**
 * Utility class for managing different states (success, error, helper) of a [TextInputLayout].
 *
 *
 * This utility class allows you to easily switch between success, error, and idle states of a
 * TextInputLayout by providing appropriate colors and text for each state.
 *
 * @param textInputLayout The TextInputLayout to be managed by this utility class.
 * @param successColorAttr The theme attribute for the color in success mode.
 *        Default value is [com.google.android.material.R.attr.colorTertiary].
 * @param errorColorAttr The theme attribute for the color in error mode.
 *        Default value is [com.google.android.material.R.attr.colorError].
 * @param idleColorAttr The theme attribute for the color in idle mode.
 *        Default value is [com.google.android.material.R.attr.colorPrimary].
 */
class TextInputLayoutUtil(
    private val textInputLayout: TextInputLayout,
    @AttrRes private val successColorAttr: Int = com.google.android.material.R.attr.colorTertiary,
    @AttrRes private val errorColorAttr: Int = com.google.android.material.R.attr.colorError,
    @AttrRes private val idleColorAttr: Int = com.google.android.material.R.attr.colorPrimary,
) {

    /**
     * Gets the context of the [TextInputLayout].
     */
    private val context: Context
        get() = textInputLayout.context

    /**
     * Sets the [TextInputLayout] to success mode with the specified success text.
     *
     * @param successText The text to be displayed in the success mode.
     */
    fun setSuccessMode(successText: CharSequence?) {
        textInputLayout.setError(successText)
        textInputLayout.setErrorIconDrawable(R.drawable.ic_success)

        val successColorStateList = getColorStateListFromAttr(successColorAttr)
        textInputLayout.setErrorTextColor(successColorStateList)
        textInputLayout.setHintTextColor(successColorStateList)
        textInputLayout.setErrorIconTintList(successColorStateList)
        textInputLayout.setBoxStrokeErrorColor(successColorStateList)
    }

    /**
     * Sets the [TextInputLayout] to error mode with the specified error text.
     *
     * @param errorText The text to be displayed in the error mode.
     */
    fun setErrorMode(errorText: CharSequence?) {
        textInputLayout.setError(errorText)
        textInputLayout.setErrorIconDrawable(R.drawable.ic_error)

        val errorColorStateList = getColorStateListFromAttr(errorColorAttr)
        textInputLayout.setErrorTextColor(errorColorStateList)
        textInputLayout.setHintTextColor(errorColorStateList)
        textInputLayout.setErrorIconTintList(errorColorStateList)
        textInputLayout.setBoxStrokeErrorColor(errorColorStateList)
    }

    /**
     * Sets the [TextInputLayout] to helper text mode with the specified helper text.
     *
     * @param helperText The text to be displayed in the helper text mode.
     */
    fun setHelperTextMode(helperText: CharSequence?) {
        textInputLayout.setError(null)

        val hintColorStateList = getColorStateListFromAttr(idleColorAttr)
        textInputLayout.setHintTextColor(hintColorStateList)
        textInputLayout.setHelperText(helperText)
    }

    /**
     * Retrieves a [ColorStateList] based on the specified theme attribute.
     *
     * @param attr The theme attribute to retrieve the color for.
     * @return A [ColorStateList] based on the specified theme attribute.
     */
    private fun getColorStateListFromAttr(
        @AttrRes attr: Int
    ): ColorStateList? {
        val colorResId = context.resolveColorResIdFromAttribute(attr)
        return ContextCompat.getColorStateList(context, colorResId)
    }
}

fun Context.resolveColorResIdFromAttribute(@AttrRes attrId: Int): Int {
    var typedArray: TypedArray? = null
    return try {
        typedArray = obtainStyledAttributes(intArrayOf(attrId))
        typedArray.getResourceId(0, 0)
    } finally {
        typedArray?.recycle()
    }
}

I am unsure if this is the best way. And, I still kinda feel like I should do this using theming/styling/selectors, but I don't know if something like that is possible.

I didn't want to extend the TextInputLayout in the end because this implementation can work without a custom class. This, also, saves me from managing the state of the view.

This is how it looks as of now (Material3):

error error-mode

success success-mode

idle/helper-text idle/helper-text-mode