Populate and manipulate AutoCompleteTextView in Android

If you ever encounter AutoCompleteTextView in your Android project, you must have had the following questions in your mind.
– How to provide data for AutoCompleteTextView
– How to design row item for AutoCompleteTextView
– How to check which item selected in AutoCompleteTextView
– How to populate AutoCompleteTextView from an array of data class without implementing an adapter
– How to change search(filter) preferences
– etc…

In this tutorial, I will answer all your questions.

Here we use Kotlin language for creating this example.
First of all, we will create the layout for the AutoCompleteTextView.

<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:layout_width="match_parent"
  android:layout_height="match_parent"
  android:paddingLeft="@dimen/activity_horizontal_margin"
  android:paddingTop="@dimen/activity_vertical_margin"
  android:paddingRight="@dimen/activity_horizontal_margin"
  android:paddingBottom="@dimen/activity_vertical_margin"
  tools:context="com.acomputerengineer.AutoCompleteTextViewActivity">
  
  <com.google.android.material.textfield.TextInputLayout
    android:id="@+id/til"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent">

    <AutoCompleteTextView
      android:id="@+id/actv"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:hint="@string/your_hint_here"
      android:inputType="text"
      android:maxLines="1" />
  </com.google.android.material.textfield.TextInputLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

Now we will see 3 different cases for AutoCompleteTextView.

First Case
Here we use array of strings to populate AutoCompleteTextView and display list.

First, we will create the list of movies as a string array and display it in AutoCompleteTextView using the default ArrayAdapter.

val movies = arrayOf("Avengers: Endgame", "Captain Marvel", "Shazam!", "Spider-Man: Far From Home", "Dark Phoenix", "Hellboy", "Glass", "Reign of the Superman", "Brightburn")
val adapter1 = ArrayAdapter(this, android.R.layout.select_dialog_item, movies)

//actv is the AutoCompleteTextView from your layout file
actv.threshold = 1 //start searching for values after typing first character
actv.setAdapter(adapter1)

Here is the result:

AutoCompleteTextView in Android
AutoCompleteTextView in Android

Second Case
Here we use an array of a custom object of data class to populate AutoCompleteTextView and display list.
We will create the list of movies as a Movie data class array and display it in AutoCompleteTextView using the default ArrayAdapter. The ArrayAdapter uses “toString()” method of a data class if there is data class array provided.

So first we will create a data class named Movie.

data class Movie(
  val name: String,
  val year: Int
) {
  override fun toString(): String {
    return name.plus(" - ").plus(year)
  }
}

Now we will create the adapter and set it to the AutoCompleteTextView.

val movieObjects = arrayOf(Movie("Avengers: Endgame", 2019), Movie("Captain Marvel", 2019), Movie("Shazam!", 2019), Movie("Spider-Man: Far From Home", 2019), Movie("Dark Phoenix", 2019), Movie("Hellboy", 2019), Movie("Glass", 2019), Movie("Reign of the Superman", 2019), Movie("Brightburn", 2019))
val adapter2 = ArrayAdapter(this, android.R.layout.select_dialog_item, movieObjects)

//actv is the AutoCompleteTextView from your layout file
actv.threshold = 1 //start searching for values after typing first character
actv.setAdapter(adapter2)

Here is the result:

AutoCompleteTextView in Android
AutoCompleteTextView in Android

Now in both above case, you may have noticed that the design for the row is single text line provided in ‘android.R.layout.select_dialog_item’ layout. If we want to create a custom layout, we need to create a custom ArrayAdapter and inflate our layout there.

You may also have noticed that here when I type “ma”, it only displays one result “Captain Marvel” because the default filter uses “startsWith()” function so it only selects the result which has words start with “ma”. So to check in the whole string, we need to create a custom filter in the ArrayAdapter. So let’s see that.

Third Case
Here first we create an adapter that extends ArrayAdapter and in that we will inflate our custom layout and also add logic for our filter method.

class AutoCompleteTextViewAdapter(private val c: Context, @LayoutRes private val layoutResource: Int, private val movies: Array<AutoCompleteTextViewActivity.Movie>) :
  ArrayAdapter<AutoCompleteTextViewActivity.Movie>(c, layoutResource, movies) {

  var filteredMovies: List<AutoCompleteTextViewActivity.Movie> = listOf()

  override fun getCount(): Int = filteredMovies.size

  override fun getItem(position: Int): AutoCompleteTextViewActivity.Movie = filteredMovies[position]

  override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
    val view = convertView ?: LayoutInflater.from(c).inflate(layoutResource, parent, false)

    view.tvMovieName.text = filteredMovies[position].name
    view.tvMovieYear.text = filteredMovies[position].year.toString()

    return view
  }

  override fun getFilter(): Filter {
    return object : Filter() {
      override fun publishResults(charSequence: CharSequence?, filterResults: FilterResults) {
        @Suppress("UNCHECKED_CAST")
        filteredMovies = filterResults.values as List<AutoCompleteTextViewActivity.Movie>
        notifyDataSetChanged()
      }

      override fun performFiltering(charSequence: CharSequence?): FilterResults {
        val queryString = charSequence?.toString()?.toLowerCase()

        val filterResults = FilterResults()
        filterResults.values = if (queryString == null || queryString.isEmpty())
            movies.asList()
          else
            movies.filter {
              it.name.toLowerCase().contains(queryString) || it.year.toString().contains(queryString)
            }
        return filterResults
      }
    }
  }
}

Here is the layout for this adapter “item_auto_complete_text_view.xml”

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:paddingLeft="@dimen/activity_horizontal_margin"
  android:paddingTop="@dimen/activity_vertical_margin"
  android:paddingRight="@dimen/activity_horizontal_margin"
  android:paddingBottom="@dimen/activity_vertical_margin">

  <TextView
    android:id="@+id/tvMovieName"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

  <TextView
    android:id="@+id/tvMovieYear"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_marginTop="5dp"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@id/tvMovieName" />

</androidx.constraintlayout.widget.ConstraintLayout>

Now we use this adapter.

val movieObjects = arrayOf(Movie("Avengers: Endgame", 2019), Movie("Captain Marvel", 2019), Movie("Shazam!", 2019), Movie("Spider-Man: Far From Home", 2019), Movie("Dark Phoenix", 2019), Movie("Hellboy", 2019), Movie("Glass", 2019), Movie("Reign of the Superman", 2019), Movie("Brightburn", 2019))
val adapter3 = AutoCompleteTextViewAdapter(this,  R.layout.item_auto_complete_text_view, movieObjects1)

//actv is the AutoCompleteTextView from your layout file
actv.threshold = 1 //start searching for values after typing first character
actv.setAdapter(adapter3)

See the result here:

AutoCompleteTextView in Android
AutoCompleteTextView in Android

Here you can see we have a custom layout where we have name and year below that. You also notice one thing that we now have all the result which contains string “ma” in the result. Because we use “contains()” function in our filter in the adapter.

Now we will see the listeners for AutoCompleteTextView which helps us to determine if the user selects any result from the suggestion or type any new text.

actv.setOnItemClickListener { adapterView, view, i, l ->
  val movie = adapterView.getItemAtPosition(i)
  //for adapter2 and adapter3
  if (movie is Movie) {
    Toast.makeText(this, "Selected item: ".plus(movie.name), Toast.LENGTH_LONG).show()
  }
  //for adapter1
  else if (movie is String) {
    Toast.makeText(this, "Selected item: ".plus(movie), Toast.LENGTH_LONG).show()
  }
}

Here you can check if the item is selected or not from the suggestions. You can also implement TextWatcher in the AutoCompleteTextView to check if the value is edited or not.

You can find the whole code in my github repo: https://github.com/dakshbhatt21/a-computer-engineer

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s