material-components / material-components-android

Modular and customizable Material Design UI components for Android
Apache License 2.0
16.37k stars 3.07k forks source link

[TextInputLayout] OnFocusChangeListener is only notified on first gained focus #1015

Open mpost opened 4 years ago

mpost commented 4 years ago

Description: Settings a OnFocusChangeListener on the TextInputEditText inside the TextInputLayout notifies the listener only the very first time the focus is gained.

Expected behavior: The listener should always fire when focus is gained and lost.

Source code:

textInputEditText.setOnFocusChangeListener { _, hasFocus -> 
  println("only fires on first focus gained")
}

Android API version: 29

Material Library version: 1.2.0-alpha04

Device: Emulator

leticiarossi commented 4 years ago

Hi, I'm not able to reproduce the issue. Could you provide a sample app?

mpost commented 4 years ago

Ok, closing this issue.

kazougagh commented 4 years ago

Hi, I have the same issue here. Why this ticket has been closed ? Is there a fix available ? Thanks

leticiarossi commented 4 years ago

@kazougagh can you provide a sample app with the issue since it's not reproducible on our end?

kazougagh commented 4 years ago

Hi @leticiarossi , This issue occurs when you try to set the endIconMode programmatically and not using the xml attributes. Like this:

`

 val inputLayout = findViewById<TextInputLayout>(R.id.text_input_layout)
 inputLayout.editText?.onFocusChangeListener = View.OnFocusChangeListener { _, hasFocus ->

        // ----------------------------
        // Called on first focus only !
        // ----------------------------

        Log.d(TAG, "inputEditText - hasFocus $hasFocus")
        if (inputLayout.endIconMode == TextInputLayout.END_ICON_NONE) {
            inputLayout.endIconMode = TextInputLayout.END_ICON_CLEAR_TEXT
            inputLayout.setEndIconTintList(ColorStateList.valueOf(getColor(R.color.colorPrimaryDark)))
        }

    }`
acarlsen commented 4 years ago

I have the exact same issue, as described by kazougagh. When setting endIconMode programatically its never called again.

D-Fox commented 3 years ago

Can we reopen this issue since it's still valid and reproducible example was provided by kazougagh?

D-Fox commented 3 years ago

Also this is relevant to comment in different issue https://github.com/material-components/material-components-android/issues/503#issuecomment-757379005

ikim24 commented 3 years ago

Both ClearTextEndIconDelegate and DropdownMenuEndIconDelegate set their own OnFocusChangeListener which replaces any OnFocusChangeListener that was previously set on the EditText.

silencieuxle commented 3 years ago

I have the same issue with this code. While debugging, the breakpoint only stopped once.

edtStreet.onFocusChangeListener =
            View.OnFocusChangeListener { _, hasFocus ->
                if (!hasFocus && !isValidField(edtStreet.text)) {
                    binding.edlStreet.setEndIconDrawable(R.drawable.ic_error_white)
                    binding.edlStreet.setEndIconOnClickListener { showToast() }
                    binding.edlStreet.endIconMode = END_ICON_CUSTOM
                    binding.edlStreet.isEndIconVisible = true
                } else {
                    binding.edlStreet.setEndIconDrawable(R.drawable.ic_clear_white)
                    binding.edlStreet.endIconMode = END_ICON_CLEAR_TEXT
                    binding.edlStreet.isEndIconVisible = true
                }
            }
koalahamlet commented 3 years ago

FWIW, I just thought I'd encountered a similar problem, when I really didn't understand how hasFocus worked.

Your current view will not lose focus unless something else if focused. This means that your parent view, or something else in your layout, should have android:focusableInTouchMode="true". That way, the edittext will actually lose focus when you click outside of the EditText, and your listener should fire just fine.

hxtruong6 commented 3 years ago

I meet the same problem :(

roklyt commented 2 years ago

I have the same problem. Any plans here?

manumathew17 commented 2 years ago

faced the same resolved by adding getEditText() before setOnfocusChangeListner textinputLayout.getEditText().setOnFocusChangeListener( ............

currentraghavkishan commented 1 year ago

Same problem as @acarlsen. When EndIconMode is set programmatically, the OnFocusChangeLinstener I set is not called. Does anyone have a solution?

zack05 commented 1 year ago

the only workaround I found is to create an inner class that sets a new listener each time:

//in code edittext.onFocusChangeListener = FocusChangeListenerWorkaround ()

private inner class FocusChangeListenerWorkaround : OnFocusChangeLIstener {

override fun onFocusChange(p0:View, p1:Boolean){ //handle focus change edittext.onFocusChangeListener = FocusChangeListenerWorkaround () } }

ukraine4ever commented 1 month ago

The issue is inside com.google.android.material.textfield.EndCompoundLayout https://github.com/material-components/material-components-android/blob/master/lib/java/com/google/android/material/textfield/EndCompoundLayout.java#L462

    if (delegate.getOnEditTextFocusChangeListener() != null) {
      editText.setOnFocusChangeListener(delegate.getOnEditTextFocusChangeListener());
    }

It overrides OnFocusChangeListener for editText if EndIconDelegate has OnEditTextFocusChangeListener. For example ClearTextEndIconDelegate has this listener.

As a workaround I created custom TextInputEditText that can have multiple OnFocusChangeListeners.

internal class CustomTextInputEditText @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null
) : TextInputEditText(context, attrs) {

    private val onFocusChangeListeners: MutableSet<OnFocusChangeListener> = mutableSetOf()

    init {
        super.setOnFocusChangeListener { v, hasFocus ->
            onFocusChangeListeners.forEach {
                it.onFocusChange(v, hasFocus)
            }
        }
    }

    override fun setOnFocusChangeListener(l: OnFocusChangeListener?) {
        if (l == null) {
            onFocusChangeListeners.clear()
        } else {
            onFocusChangeListeners.add(l)
        }
    }

    override fun getOnFocusChangeListener(): OnFocusChangeListener? =
        if (onFocusChangeListeners.isEmpty()) null else super.getOnFocusChangeListener()

}