1

My app has an AutoCompleteTextView that queries a Room database and displays the search results in the dropdown as the user types. The results are displayed correctly but the dropdown's height is always too large for the number of items (see screenshot below).

Dropdown with results

Basically the AutoCompleteTextView has a TextWatcher that sends the current text to the ViewModel, which in turn queries the database. Once the results are received, the ViewModel updates the view state (a Flow) and the fragment, which observes it, reassigns the AutoCompleteTextView's adapter with the new data.

Dropdown item layout

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/root"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="@dimen/spacing_8">

    <GridLayout
        android:id="@+id/imageContainer"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        ... />

    <com.google.android.material.textview.MaterialTextView
        android:id="@+id/txtName"
        style="@style/TextAppearance.MaterialComponents.Body1"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        ... />
</androidx.constraintlayout.widget.ConstraintLayout>

Fragment layout

<com.google.android.material.textfield.TextInputLayout>
    ...
    <com.google.android.material.textfield.MaterialAutoCompleteTextView
        android:id="@+id/autoCompleteTextView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="@dimen/spacing_16"/>
</com.google.android.material.textfield.TextInputLayout>

Adapter

internal class StationAdapter(
    context: Context,
    private val stations: List<UiStation>
) : ArrayAdapter<String>(
    context,
    R.layout.item_station,
    stations.map { it.name }
) {

    fun getStation(position: Int): UiStation = stations[position]

    override fun getCount(): Int = stations.size

    override fun getItem(position: Int): String = stations[position].name

    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
        return convertView ?: run {
            val inflater = LayoutInflater.from(context)
            val binding = ItemStationBinding.inflate(inflater, parent, false).apply {
                val station = stations[position]
                imageContainer.addIconsForModes(station.modes)
                txtName.text = station.name
            }

            binding.root
        }
    }

    ...
}

Fragment

private fun handleState(state: StationSearchViewState) = with(state) {
    ...

    stations?.let {
        val adapter = StationAdapter(
            context = requireContext(),
            stations = it
        )
        binding.autoCompleteTextView.setAdapter(adapter)
    }
}

Now these are the other things I tried as well but didn't work (got the same result):

  1. Passing all of the database table's results to the adapter and implementing a custom filter
  2. Implementing a function to submit new data to the adapter and calling notifyDataSetChanged()
  3. Setting the AutoCompleteTextView's android:dropDownHeight property to wrap_content

The only thing that seems to work is when I use an ArrayAdapter with the android.R.layout.simple_list_item_1 layout for the items

92AlanC
  • 1,327
  • 2
  • 14
  • 33

2 Answers2

0

After 3 days working hard to figure this out I finally managed to find a solution, which is partly in this answer to an unrelated question.

Here is my code:

private const val DROPDOWN_ITEM_MAX_COUNT = 5
private const val PADDING = 34

class CustomAutoCompleteTextView(
    context: Context,
    attributeSet: AttributeSet?
) : MaterialAutoCompleteTextView(context, attributeSet) {

    override fun onFilterComplete(count: Int) {
        val itemCount = if (count > DROPDOWN_ITEM_MAX_COUNT) {
            DROPDOWN_ITEM_MAX_COUNT
        } else {
            count
        }
        val individualItemHeight = (height / 2) + PADDING

        dropDownHeight = itemCount * individualItemHeight
        super.onFilterComplete(count)
    }
}

Essentially I had to create a custom AutoCompleteTextView which, once the data is filtered, takes an item count (no greater than the maximum I've set) and multiplies it by the height of an individual item on my dropdown (a) plus a padding (b) to make sure the dropdown view always wraps the items.

a: I reached this value with a bit of trial and error but it still works across different screen sizes and densities

b: not mandatory. I just thought adding a bit of padding made it look nicer

End result

Single result

Multiple results

92AlanC
  • 1,327
  • 2
  • 14
  • 33
0

I had the same problem and wasn't really happy with implementing a custom AutoCompleteTextView. But after some fiddling around, I found an easy fix.

The problem comes from the ConstraintLayout as the root of the dropdown item layout. I wrapped it with a RelativeLayout and it worked fine.

Dropdown item layout

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/root"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="@dimen/spacing_8">

        ...
    </androidx.constraintlayout.widget.ConstraintLayout>
</RelativeLayout>

I assume that a FrameLayout or a LinearLayout would work as well, but I haven't tested it.

Rimgar
  • 1