Kotlin / anko

Pleasant Android application development
Apache License 2.0
15.89k stars 1.29k forks source link

ClassCastException in TextInputLayout #264

Open k-kagurazaka opened 7 years ago

k-kagurazaka commented 7 years ago

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.

rovkinmax commented 7 years ago

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

rovkinmax commented 7 years ago

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

not3 commented 7 years ago

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)
jonathanrz commented 7 years ago

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)

Rahulkr2 commented 7 years ago

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

jonathanrz commented 7 years ago

@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.

jayrave commented 7 years ago

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

krokyze commented 7 years ago

Setting layoutParams this way works:

textInputLayout {
    editText {
        layoutParams = LinearLayout.LayoutParams(matchParent, wrapContent)
    }
}
TomislavHorvat1 commented 6 years ago

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)
}