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

Open chat page in Whatsapp for given number in Android

You can use this functionality of opening any Whatsapp number from your Android app for contact us page or in the send us feedback page. You can set your number in the code and when the user taps on the contact us button, feedback button or any other button, they will redirect to the Whatsapp app with your chat page open. So they can type and send message to you directly.

Open chat page in Whatsapp for given number
Open chat page in Whatsapp for given number

Now we will see the code for that. From now on we will use the Kotlin as our primary language.

First, we will see the layout where you can enter the phone number of the user with the country code. And then press the button to open the Whatsapp app.

<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.OpenWhatsappNumberActivity">

  <EditText
    android:id="@+id/et"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:hint="@string/enter_phone_number"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

  <Button
    android:id="@+id/btn"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:text="@string/open_in_whatsapp"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@id/et" />

</androidx.constraintlayout.widget.ConstraintLayout>

Now we will see the Kotlin code for that.

btn.setOnClickListener {
  val phoneNumber = et.text.toString()
  val url = "https://api.whatsapp.com/send?phone=$phoneNumber"
  try {
    packageManager.getPackageInfo("com.whatsapp", PackageManager.GET_ACTIVITIES)
    val i = Intent(Intent.ACTION_VIEW)
    i.data = Uri.parse(url)
    startActivity(i)
  } catch (e: PackageManager.NameNotFoundException) {
    Toast.makeText(this, "Whatsapp is not installed in your phone.", Toast.LENGTH_SHORT).show()
    e.printStackTrace()
  }
}

Github repository link: https://github.com/dakshbhatt21/a-computer-engineer

Display image grid in RecyclerView in Kotlin Android

We have this tutorial for Java here: Display image grid in RecyclerView in Android. And now this tutorial is for Kotlin specially.

First of all if you are doing Kotlin first time in Android Studio then checkout this tutorial here: https://developer.android.com/kotlin/get-started

We use RecyclerView for this tutorial using GridLayoutManager.

Images Grid in Android
Images Grid in Kotlin Android

First of all we will create layout for this post.

<?xml version="1.0" encoding="utf-8"?>
<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"
  tools:context=".ImageGridKotlinActivity">

  <androidx.recyclerview.widget.RecyclerView
    android:id="@+id/rv"
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent">

  </androidx.recyclerview.widget.RecyclerView>

</androidx.constraintlayout.widget.ConstraintLayout>

Now we create adapter which holds the ImageView which will go in the grid.

class ImageGridKotlinAdapter(private val c: Context, private val images: ArrayList<String>) :
        RecyclerView.Adapter<ImageGridKotlinAdapter.ColorViewHolder>() {


  override fun getItemCount(): Int {
    return images.size
  }

  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ColorViewHolder {
    return ColorViewHolder(LayoutInflater.from(c).inflate(R.layout.item_grid_kotlin, parent, false))
  }

  override fun onBindViewHolder(holder: ColorViewHolder, position: Int) {
    val path = images[position]

    Picasso.get()
      .load(path)
      .resize(250, 250)
      .centerCrop()
      .into(holder.iv)

    holder.iv.setOnClickListener {
      //handle click event on image
    }
  }

  class ColorViewHolder(view: View) : RecyclerView.ViewHolder(view) {
    val iv = view.iv as ImageView
  }
}

Here I used Picasso library to display image, if you want to integrate it in your project then please check their web page: http://square.github.io/picasso/

Now we will go for the layout we used in above adapter. It just hold single ImageView.

<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<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="wrap_content">

  <ImageView
    android:id="@+id/iv"
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintDimensionRatio="1:1"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Here we use the ratio constraint on the ImageView and so that we can eliminate the use of custom square image view here. It is one of the benefit of using ConstraintLayout.

Now we set the above adapter to the RecyclerView so that our Images will be displayed in the grid manner. Here we use images from Flickr public domain. In RecyclerView, we used StaggeredGridLayoutManager which tell the RecyclerView to use the grid like layout with the number of columns provided.

val sglm = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
rv.layoutManager = sglm

val imageList = ArrayList<String>()
imageList.add("https://farm5.staticflickr.com/4403/36538794702_83fd8b63b7_c.jpg")
imageList.add("https://farm5.staticflickr.com/4354/35684440714_434610d1d6_c.jpg")
imageList.add("https://farm5.staticflickr.com/4301/35690634410_f5d0e312cb_c.jpg")
imageList.add("https://farm4.staticflickr.com/3854/32890526884_7dc068fedd_c.jpg")
imageList.add("https://farm8.staticflickr.com/7787/18143831616_a239c78056_c.jpg")
imageList.add("https://farm9.staticflickr.com/8745/16657401480_57653ac8b0_c.jpg")
imageList.add("https://farm3.staticflickr.com/2917/14144166232_44613c53c7_c.jpg")
imageList.add("https://farm8.staticflickr.com/7453/13960410788_3dd02b7a02_c.jpg")
imageList.add("https://farm1.staticflickr.com/920/29297133218_de38a7e4c8_c.jpg")
imageList.add("https://farm2.staticflickr.com/1788/42989123072_6720c9608d_c.jpg")
imageList.add("https://farm1.staticflickr.com/888/29062858008_89851766c9_c.jpg")
imageList.add("https://farm2.staticflickr.com/1731/27940806257_8067196b41_c.jpg")
imageList.add("https://farm1.staticflickr.com/884/42745897912_ff65398e38_c.jpg")
imageList.add("https://farm2.staticflickr.com/1829/27971893037_1858467f9a_c.jpg")
imageList.add("https://farm2.staticflickr.com/1822/41996470025_414452d7a0_c.jpg")
imageList.add("https://farm2.staticflickr.com/1793/42937679651_3094ebb2b9_c.jpg")
imageList.add("https://farm1.staticflickr.com/892/42078661914_b940d96992_c.jpg")
val igka = ImageGridKotlinAdapter(this, imageList)
rv.adapter = igka

Here we used AndroidX which is major improvement over the Android Support Library. You can find out more information here: https://developer.android.com/jetpack/androidx

I also introduced ConstraintLayout for the first time in this project repo. Here we eliminated the SquareImageView class that we used in Java Image GridAdapter because of ratio constraint of ConstraintLayout.

You can find the source code for this on my github repo: https://github.com/dakshbhatt21/a-computer-engineer

Please let me know if you find any issue in the above code or have any difficulty in implementing that.