androidandroid-textwatcher

Bug With TextInputLayout on Configuration Change


I'm using TextInputLayout in my application and when I add a TextWatcher to it's TextInputEditText that sets an error on the TextInputLayout, my app crashes when I trigger a configuration change.

editText.addTextChangedListener(object : TextWatcher {
    override fun afterTextChanged(text: Editable) {
        // no-op
    }
    override fun beforeTextChanged(text: CharSequence, start: Int, count: Int, after: Int) {
        // no-op
    }
    override fun onTextChanged(text: CharSequence, start: Int, before: Int, count: Int) {
        textInputLayout.error = if (text.isBlank()) {
            getString(R.string.error_text)
        } else {
            null
        }
    }
})

The stacktrace is

java.lang.NullPointerException: Attempt to read from field 'int android.view.View.mViewFlags' on a null object reference at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3863) at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3864) at com.google.android.material.textfield.TextInputLayout.dispatchRestoreInstanceState(TextInputLayout.java:2053) at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3864) at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3864) at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3864) at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3864) at android.view.View.restoreHierarchyState(View.java:19808) at androidx.fragment.app.Fragment.restoreViewState(Fragment.java:539) at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:907) at androidx.fragment.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManagerImpl.java:1238) at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:1303) at androidx.fragment.app.FragmentManagerImpl.dispatchStateChange(FragmentManagerImpl.java:2656) at androidx.fragment.app.FragmentManagerImpl.dispatchActivityCreated(FragmentManagerImpl.java:2610) at androidx.fragment.app.FragmentController.dispatchActivityCreated(FragmentController.java:246) at androidx.fragment.app.FragmentActivity.onStart(FragmentActivity.java:542) at androidx.appcompat.app.AppCompatActivity.onStart(AppCompatActivity.java:201) at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1425) at android.app.Activity.performStart(Activity.java:7825) at android.app.ActivityThread.handleStartActivity(ActivityThread.java:3294) at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:221) at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:201) at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:173) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016) at android.os.Handler.dispatchMessage(Handler.java:107) at android.os.Looper.loop(Looper.java:214) at android.app.ActivityThread.main(ActivityThread.java:7356) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)


Solution

  • It seems like you might need to override onViewStateRestored and remove/add your TextWatcher from there.

    You should also save your TextWatcher somewhere that can survive a configuration change such as an Android ViewModel.

    Here's a quick example:

    // Inside of onViewStateRestored
    
    if (viewModel.textWatcher != null) {
        editText.removeTextChangedListener(viewModel.textWatcher)
        viewModel.textWatcher = null
    }
    
    // Create and add TextWatcher back