John Ward
John Ward

Reputation: 940

AutoCompleteTextView: detecting when dropdown is dismissed and item is NOT selected

I need to know when the user taps outside of the AutoCompleteTextView dropdown in order to dismiss it (i.e. they dismiss the popup with selecting an item in the list). I've setup the setOnDismissListener() as shown here:

    mAutoView.setOnDismissListener(new AutoCompleteTextView.OnDismissListener() {
        @Override
        public void onDismiss() {
            CharSequence msg = "isPerformingCompletion = " + mAutoView.isPerformingCompletion() +
                    "   Item selected at = " + mAutoView.getListSelection();
            Toast.makeText(getContext(), msg, Toast.LENGTH_LONG).show();
        }
    });

And an OnItemClickListener like this:

 private AdapterView.OnItemClickListener mAutocompleteClickListener
        = new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        // get selected item and pass it to result callback
    }
};

The onDismiss event fires before the onItemClick event, and unfortunately, neither the "isPerformingCompletion()" nor the "getListSelection()" methods return a value until the onItemClick event fires.

Can anyone suggest an approach to detecting a dismiss without a list selection?

Upvotes: 3

Views: 1873

Answers (2)

Zain
Zain

Reputation: 40878

AutoCompleteTextView: detecting when dropdown is dismissed and item is NOT selected

If I understood you well, you need to catch the event of dismissing the draopDown by touching outside of it instead of choosing an item.

For some reason, the setOnDismissListener doesn't work for me. I couldn't find a clue without touching the inner ListPopupWindow which get called on either event (item click or touch outside).

The event is differentiated by registering an inner OnItemClickListener listener to set the tag of the view to some value that indicates that BY_ITEM_CLICK ; if the tag is anything else; it will be considered a touch outside event.

Here is a custom AutoCompleteTextView that can be used for that:

import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import android.view.KeyEvent
import android.view.View
import android.widget.AdapterView
import android.widget.AutoCompleteTextView
import android.widget.ListPopupWindow
import androidx.appcompat.widget.AppCompatAutoCompleteTextView


class OnDismissAutoCompleteTextView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null
) : AppCompatAutoCompleteTextView(context, attrs), AdapterView.OnItemClickListener {

    interface OnMenuDismissListener {
        fun onTouchOutside()
        fun onItemClick()
        fun onBackPressed()
    }

    companion object {

        // set the tag to this value when an item is clicked to dismiss the menu
        const val BY_ITEM_CLICK = "BY_ITEM_CLICK"

        // set the tag to this value when the back is pressed to dismiss the menu
    }

    init {
        super.setOnItemClickListener(this)
    }

    var onMenuDismissListener: OnMenuDismissListener? = null

    private var consumerListener: AdapterView.OnItemClickListener? = null


    @SuppressLint("DiscouragedPrivateApi")
    private fun getPopup(): ListPopupWindow? {
        try {
            val field = AutoCompleteTextView::class.java.getDeclaredField("mPopup")
            field.isAccessible = true
            return field.get(this) as ListPopupWindow
        } catch (e: NoSuchFieldException) {
            e.printStackTrace()
        } catch (e: IllegalAccessException) {
            e.printStackTrace()
        }
        return null
    }

    private fun setupDismissListener() {
        getPopup()?.let {
            it.setOnDismissListener {

                when (tag) {
                    BY_ITEM_CLICK -> onMenuDismissListener?.onItemClick() // Menu dismissal Event of clicking on the menu item

                    BY_BACK_PRESSED -> onMenuDismissListener?.onBackPressed()

                    else -> onMenuDismissListener?.onTouchOutside() // Menu dismissal Event of touching outside the menu
                }
                // reset the tag for the next dismissal
                tag = null
            }

        }
    }

    override fun onPreDraw(): Boolean {
        // Registering the mPopup window OnDismissListener
        setupDismissListener()
        return super.onPreDraw()
    }

    override fun onItemClick(p0: AdapterView<*>?, p1: View?, p2: Int, p3: Long) {
        tag = BY_ITEM_CLICK
        consumerListener?.onItemClick(p0, p1, p2, p3)
    }

    override fun setOnItemClickListener(l: AdapterView.OnItemClickListener?) {
        // DO NOT CALL SUPER HERE
//        super.setOnItemClickListener(l)
        consumerListener = l
    }

    override fun onKeyPreIme(keyCode: Int, event: KeyEvent?): Boolean {
        if (keyCode == KeyEvent.KEYCODE_BACK && isPopupShowing)
            tag = BY_BACK_PRESSED
        return super.onKeyPreIme(keyCode, event)
    }

}

Usage:

myAutoCompleteTV.onMenuDismissListener = object :
    OnDismissAutoCompleteTextView.OnMenuDismissListener {
    override fun onTouchOutside() {
        // Menu dismiss is due to touch outside event
        Toast.makeText(context, "touch outside", Toast.LENGTH_SHORT).show()
    }

    override fun onItemClick() {
        // Menu dismiss is due to clicking on an item
        Toast.makeText(context, "item clicked", Toast.LENGTH_SHORT).show()
    }

    override fun onBackPressed() {
        Toast.makeText(context, "back pressed", Toast.LENGTH_SHORT).show()
    }
}

Upvotes: 1

Rahul
Rahul

Reputation: 641

Below piece of code will detect that if user dismiss the dropdown of autocompletetextview, Then onDismiss it will check the selected input using Geocode API, If it is having a result then selected input is valid otherwise input is not valid, So In that case, Entered text will be vanish in bit seconds.

mAutoView.setOnDismissListener {
    try {
        val fromLocationName = Geocoder(context).getFromLocationName(mAutoView.getText().toString(), 1)
        if (fromLocationName != null && fromLocationName.isNotEmpty()) {
            Log.d(TAG, "Address valid")
        } else {
            mAutoView.setText("")
            Log.d(TAG, "Address not valid")
        }
    } catch (e: Exception) {
        mAutoView.setText("")
        Log.d(TAG, "Address not valid with Exception")
    }
}

Means you need a kind of validator in OnDismiss, That will check the input text is valid or not, Based on validation you can indicate to user that entered input is valid or not.

Upvotes: 2

Related Questions