Faisal
Faisal

Reputation: 1472

Android: TextInputLayout ColorStateList.isStateful() throws exception when called from setErrorEnabled()

The custom TextInputLayout results in the below exception when trying to achieve errorState.

  Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.content.res.ColorStateList.isStateful()' on a null object reference
        at com.google.android.material.textfield.TextInputLayout.setBoxStrokeColorStateList(TextInputLayout.java:1103)
        at tech.********.platform.components.MyCustomTextInput.setErrorEnabled(MyCustomTextInput.kt:81)
        at com.google.android.material.textfield.TextInputLayout.<init>(TextInputLayout.java:786)
        at com.google.android.material.textfield.TextInputLayout.<init>(TextInputLayout.java:422)
        at tech.********.platform.components.MyCustomTextInput.<init>(MyCustomTextInput.kt:38)

XML code to switch app:errorEnabled with binding

<MyCustomTextInput>
  android:id="@+id/inputPassword"
  ...
  app:errorEnabled="@{condition ? true: false}"
<MyCustomTextInput/>

trying to achieve error stoke colour without error text as below:

override fun setErrorEnabled(enabled: Boolean) {
   if (enabled) setBoxStrokeColorStateList(errorColor)
   else setBoxStrokeColorStateList(defaultColor)
   super.setErrorEnabled(false)
}

complete Layout code below:

class MyCustomTextInput : TextInputLayout {

    constructor(context: Context) : super(context)

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    )

    /**
     * Color states for the default scenario.
     */
    private var defaultColor = ColorStateList(
        arrayOf(
            intArrayOf(-R.attr.state_focused, R.attr.state_enabled),
            intArrayOf(R.attr.state_focused, R.attr.state_enabled),
        ), intArrayOf(Color.parseColor("#CCCCCC"), Color.parseColor("#164A9C"))
    )

    /**
     * Color states for the error scenario.
     */
    private var errorColor = ColorStateList(
        arrayOf(
            intArrayOf(-R.attr.state_focused, R.attr.state_enabled),
            intArrayOf(R.attr.state_focused, R.attr.state_enabled)
        ), intArrayOf(Color.RED, Color.RED)
    )

    init {
        setBoxStrokeColorStateList(defaultColor) // edit text stroke
        hintTextColor = defaultColor // edit text content
    }

    override fun setErrorIconDrawable(errorIconDrawable: Drawable?) {
        super.setErrorIconDrawable(null)
    }

    override fun setErrorIconTintList(errorIconTintList: ColorStateList?) {
        super.setErrorIconTintList(errorColor)
    }

    override fun setErrorEnabled(enabled: Boolean) {
        if (enabled) setBoxStrokeColorStateList(errorColor)
        else setBoxStrokeColorStateList(defaultColor)
        super.setErrorEnabled(false)
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
    }
}

Using the same colorState as used in the init().

Upvotes: 0

Views: 184

Answers (1)

Artem Viter
Artem Viter

Reputation: 854

Problem in your overriding of setErrorEnabled method: TextInputLayout call to them from constructor but variables in MyCustomTextInput (child class) not initialized because first the runtime execute a Parent constructor and then child initializers and constructor.

See simple sample bellow. Parent.java:

public class Parent {

    public Parent() {
        System.out.println("Parent constructor execution.");
        System.out.println("Parent call to printErrorMessageLength.");
        printErrorMessageLength("Parent error.");
    }

    public void printErrorMessageLength(String message) {
        System.out.println("Parent printErrorMessageLength execution.");
        System.out.println(message.length());
    }
}

Test.kt:

fun main() {
    Child()
}

class Child : Parent {
    var childErrorMessage = "Child Error"

    init {
        println("Child.init, childErrorMessage = $childErrorMessage")
    }

    constructor() : super() {} // just for sample

    override fun printErrorMessageLength(inputMessage: String?) {
        println("Child printErrorMessageLength execution with inputMessage = $inputMessage")
        println("${childErrorMessage.length}")
        super.printErrorMessageLength(inputMessage)
    }
}

result of main executiong:

Parent constructor execution.
Parent call to printErrorMessageLength.
Child printErrorMessageLength execution with inputMessage = Parent error.
Exception in thread "main" java.lang.NullPointerException
    at Child.printErrorMessageLength(Test.kt:16)
    at Parent.<init>(Parent.java:6)
    at Child.<init>(Test.kt:12)
    at TestKt.main(Test.kt:2)
    at TestKt.main(Test.kt)

Process finished with exit code 1

How I can solve this problem ? - initialize variable if it a null:

/**
     * Color states for the default scenario.
     */
    private var defaultColor = createDefaultColor()

private fun createDefaultColor(): ColorStateList = ColorStateList(
        arrayOf(
            intArrayOf(-R.attr.state_focused, R.attr.state_enabled),
            intArrayOf(R.attr.state_focused, R.attr.state_enabled),
        ), intArrayOf(Color.parseColor("#CCCCCC"), Color.parseColor("#164A9C"))
    )

    /**
     * Color states for the error scenario.
     */
    private var errorColor = createErrorColor()

private fun createErrorColor(): ColorStateList = ColorStateList(
        arrayOf(
            intArrayOf(-R.attr.state_focused, R.attr.state_enabled),
            intArrayOf(R.attr.state_focused, R.attr.state_enabled)
        ), intArrayOf(Color.RED, Color.RED)
    )

override fun setErrorEnabled(enabled: Boolean) {
        if (enabled) setBoxStrokeColorStateList(errorColor?:createErrorColor())
        else setBoxStrokeColorStateList(defaultColor?:createDefaultColor())
        super.setErrorEnabled(false)
    }

Or u can use method like getDefaultColor() instead using variable defaultColor:

private fun getDefaultColor(): ColorStateList {
  if(defaultColor == null) {
    defaultColor = createDefaultColor()
  }
  return defaultColor
}

Upvotes: 0

Related Questions