Skip to content

Instantly share code, notes, and snippets.

@fonix232
Last active August 16, 2021 07:43
Show Gist options
  • Save fonix232/6553f66d17746dca5d062eecf40e8b3b to your computer and use it in GitHub Desktop.
Save fonix232/6553f66d17746dca5d062eecf40e8b3b to your computer and use it in GitHub Desktop.
AutoCompleteTextView databinding
@BindingAdapter("valueAttrChanged")
fun AutoCompleteTextView.setListener(listener: InverseBindingListener?) {
this.onItemSelectedListener = if (listener != null) {
object : AdapterView.OnItemSelectedListener {
override fun onNothingSelected(parent: AdapterView<*>?) {
listener.onChange()
}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
listener.onChange()
}
}
} else {
null
}
}
@get:InverseBindingAdapter(attribute = "value")
@set:BindingAdapter("value")
var AutoCompleteTextView.selectedValue: Any?
get() = if (listSelection != ListView.INVALID_POSITION) adapter.getItem(listSelection) else null
set(value) {
val newValue = value ?: adapter.getItem(0)
setText(newValue.toString(), true)
if (adapter is ArrayAdapter<*>) {
val position = (adapter as ArrayAdapter<Any?>).getPosition(newValue)
listSelection = position
}
}
@BindingAdapter("entries", "itemLayout", "textViewId", requireAll = false)
fun AutoCompleteTextView.bindAdapter(entries: Array<Any?>, @LayoutRes itemLayout: Int?, @IdRes textViewId: Int?) {
val adapter = when {
itemLayout == null -> {
ArrayAdapter(context, R.layout.item_dropdown, R.id.dropdownText, entries)
}
textViewId == null -> {
ArrayAdapter(context, itemLayout, entries)
}
else -> {
ArrayAdapter(context, itemLayout, textViewId, entries)
}
}
setAdapter(adapter)
}
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/tilGender"
style="@style/AppTheme.DropDownMenu"
android:prompt="@string/label_gender"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tilLastName">
<AutoCompleteTextView
android:hint="@string/label_gender"
android:layout_width="match_parent"
android:layout_height="wrap_content"
entries="@{@stringArray/genders}"
value="@={viewModel.gender}"
android:editable="false"/>
</com.google.android.material.textfield.TextInputLayout>
<style name="AppTheme.DropDownMenu" parent="Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu">
<item name="android:layout_width">0dp</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_marginTop">@dimen/margin_regular</item>
<item name="android:layout_marginBottom">@dimen/margin_regular</item>
<item name="android:layout_marginStart">@dimen/margin_large</item>
<item name="android:layout_marginEnd">@dimen/margin_large</item>
<item name="hintTextColor">@color/colorpalette_blue</item>
<item name="boxStrokeColor">@color/outlined_stroke_color</item>
</style>
@chinhnguyen
Copy link

Hi, the OnItemSelectedListener approach is not working anymore.

I managed to use the OnItemClickListener as below:

@BindingAdapter("valueAttrChanged")
fun AutoCompleteTextView.setListener(listener: InverseBindingListener?) {
    onItemClickListener = listener?.let {
        AdapterView.OnItemClickListener { _, _, position, _ ->
            setTag(R.id.autoCompleteTextViewListSelection, position)
            it.onChange()
        }
    }
}

@robsenshuu
Copy link

Hi! thanks for sharing!

I'm having issue trying to implement this.

the issue here is: adapter is always null and viewModel.gender never updates with the new value

@get:InverseBindingAdapter(attribute = "value")
@set:BindingAdapter("value")
var AutoCompleteTextView.selectedValue: Any?
    get() = if (listSelection != ListView.INVALID_POSITION) adapter.getItem(listSelection) else null
    set(value) {
        val newValue = value ?: adapter.getItem(0) // crash because adapter is null 
        setText(newValue.toString(), true)
        if (adapter is ArrayAdapter<*>) { // ALWAYS returns false
            val position = (adapter as ArrayAdapter<Any?>).getPosition(newValue)
            listSelection = position
        }
    }

I've been searching for how to solve this issue but I haven't had succeed. Not sure if all these code still working.

Could you help me? Thanks again

@chinhnguyen
Copy link

@Roberasd can you paste the code that use the binding as else?

@Berki2021
Copy link

Berki2021 commented Sep 14, 2020

I am using this approach and it works:

@BindingAdapter("dropdown_text")
fun setDropDownText(view: ExposedDropDown, liveData: MutableLiveData<String>) {
    if (liveData.value != null) {
        if (!view.text.equals(liveData.value)) {
            view.setText(liveData.value, false)
        }
    }
}

@InverseBindingAdapter(attribute = "dropdown_text")
fun getDropDownText(view: ExposedDropDown): String {
    return view.text.toString()
}

@BindingAdapter("dropdown_textAttrChanged")
fun setDropDownListener(view: ExposedDropDown, listener: InverseBindingListener) {
    view.setOnItemClickListener { _, _, _, _ -> listener.onChange() }
}

In my XML:

<com.example.app.framework.ui.view.utils.ExposedDropDown
                android:id="@+id/calibrate_option_shipping_first_dropdown"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:inputType="none"
                app:dropdown_adapter="@{@stringArray/calibrate_option_shipping_first_menu_context}"
                app:dropdown_text="@={vm.shippingFirstDdText}"/>

ExposedDropDown is my custom class that inherits AutoCompleteTextView

The only problem I have when I use another onItemClickListener that my liveData gets erased aka. never set

@BindingAdapter("dropdown_behavior")
fun setDropDownBehavior(view: ExposedDropDown, behavior: (Int) -> Unit) {
    view.setOnItemClickListener { _: AdapterView<*>, _: View, selectedItem: Int, _: Long ->
        behavior(selectedItem)
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment