androidandroid-textinputlayout

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


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.


Solution

  • 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