Anko: ClassCastException in TextInputLayout

Created on 13 Oct 2016  路  9Comments  路  Source: Kotlin/anko

I found an issue that the below code throw java.lang.ClassCastException: android.widget.LinearLayout$LayoutParams cannot be cast to android.widget.FrameLayout$LayoutParams:

    // in Activity
   override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        frameLayout {
            textInputLayout {
                textInputEditText().lparams(width = matchParent)
            }
        }
    }

// extension methods for DSL
inline fun ViewManager.textInputEditText() = textInputEditText {}
inline fun ViewManager.textInputEditText(theme: Int = 0, init: TextInputEditText.() -> Unit)
        = ankoView(::TextInputEditText, theme, init)

However the below code works well:

   override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        frameLayout {
            textInputLayout {
                textInputEditText {
                    layoutParams = LinearLayout.LayoutParams(
                            ViewGroup.LayoutParams.MATCH_PARENT,
                            ViewGroup.LayoutParams.WRAP_CONTENT)
                }
            }
        }
    }

In my understanding, both codes roughly the same, what's happen?
The issue is occurred when using Support Library version >= v 24.2.0.

Thanks.

Anko Layouts bug

Most helpful comment

Setting layoutParams this way works:

textInputLayout {
    editText {
        layoutParams = LinearLayout.LayoutParams(matchParent, wrapContent)
    }
}

All 9 comments

I have the same issue.
@k-kagurazaka you found solution?

I use org.jetbrains.anko:anko-design:0.9.1
For example, simple code

class ActivityComponent : AnkoComponent<Activity> {

    override fun createView(ui: AnkoContext<Activity>): View = with(ui) {
        textInputLayout {
            editText().lparams(width = matchParent)
        }
    }
}

throw java.lang.ClassCastException: android.widget.LinearLayout$LayoutParams cannot be cast to android.widget.FrameLayout$LayoutParams

I use version:0.10.0-beta-1, throw java.lang.ClassCastException: android.widget.LinearLayout$LayoutParams cannot be cast to android.widget.FrameLayout$LayoutParams

textInputLayout {
                        emailView = autoCompleteTextView {
                            hintResource = R.string.prompt_email
                            inputType = InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
                            maxLines = 1
                        }.lparams(width = matchParent)
                    }.lparams(width = matchParent)

I overrode the textInputLayout also and it solved the problem:
inline fun ViewManager.appCompatEditText(theme: Int = 0, init: AppCompatEditText.() -> Unit) = ankoView(::AppCompatEditText, theme, init)

inline fun ViewManager.textInputLayout(theme: Int = 0, init: TextInputLayout.() -> Unit) = ankoView(::TextInputLayout, theme, init)

@jonathanrz Could you please elaborate ? Why does this work ?

@Rahulkr2 actually I don't know. I can only think that the TextInputLayout that the native anko function textInputLayout creates is different from the TextInputLayout from android support library.

Reason for ClassCastException => setting layout params via anko's lparams sets the params only after the view is added to its parent. While this works out for other ViewGroups, it doesn't work while adding an EditText to a TextInputLayout (which by itself is a LinearLayout) because of its addView method

    @Override
    public void addView(View child, int index, final ViewGroup.LayoutParams params) {
        if (child instanceof EditText) {
            // Make sure that the EditText is vertically at the bottom, so that it sits on the
            // EditText's underline
            FrameLayout.LayoutParams flp = new FrameLayout.LayoutParams(params);
            flp.gravity = Gravity.CENTER_VERTICAL | (flp.gravity & ~Gravity.VERTICAL_GRAVITY_MASK);
            mInputFrame.addView(child, flp);

            // Now use the EditText's LayoutParams as our own and update them to make enough space
            // for the label
            mInputFrame.setLayoutParams(params);
            updateInputLayoutMargins();

            setEditText((EditText) child);
        } else {
            // Carry on adding the View...
            super.addView(child, index, params);
        }
    }

As we can see from the implementation, instead of adding the child which is an EditText to itself, it is instead added to a FrameLayout (mInputFrame). When params are set via lparams for the EditText added to TextInputLayout, it creates an instance of LinearLayout.LayoutParams & sets it on the view (which by now lives inside a FrameLayout). During the measure pass, FrameLayout assumes that all its children are going to have valid FrameLayout.LayoutParams & so tries to cast but it blows up with an exception

Setting layoutParams this way works:

textInputLayout {
    editText {
        layoutParams = LinearLayout.LayoutParams(matchParent, wrapContent)
    }
}

Why would you set lparams on the editText? The idea of the TextInputLayout is to hold an EditText, so you should set lparams on the textInpuLayout instead of the editText.

Doing it this way you get no crashes.

pwdLayout = textInputLayout {

    isPasswordVisibilityToggleEnabled = true

    pwdEditText = editText {
        inputType = TYPE_CLASS_TEXT or TYPE_TEXT_VARIATION_PASSWORD
        hint = resources.getString(R.string.login_dialog_password_hint)            
    }

}.lparams(matchParent, wrapContent){
    marginEnd = dip(24)
    marginStart = dip(24)
    bottomMargin = dip(8)
}
Was this page helpful?
0 / 5 - 0 ratings