Android Tag Input/EditText (Material Chips)

March 18, 2019
Tag input like StackOverflow or to contact/email like Gmail

There are a few ways to implement Chips (Tag input)

3rd party libraries

Or you could use Material Chips, which is more flexible but require more work.

This tutorial will use Material Chips.

The following example use AutoCompleteTextView

  • Allow multiple unique tags
  • Allow user to either select from a drop list or add a new tag by text
  • Enter their custom tag and hit enter/done on the keyboard to add tag
  • Enter their custom tag and hit comma or space to add tag
  • The added tags are shown below AutoCompleteTextView
  • Allow tags to be removed

Layout

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <AutoCompleteTextView
        android:id="@+id/mainTagAutoCompleteTextView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="textCapWords"
        android:completionThreshold="1"
        android:imeOptions="actionDone"
        />

    <com.google.android.material.chip.ChipGroup
        android:id="@+id/mainTagChipGroup"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:chipSpacingVertical="4dp"
        />

</LinearLayout>

NOTE: You can show ChipGroup (left) and AutoCompleteTextView (right) horizontally. Remove underline from AutoCompleteTextView, put a fake underline (optional) below ChipGroup and AutoCompleteTextView and set minimum length to AutoCompleteTextView.

val allTags = listOf("Love", "Passion", "Peace", "Hello", "Test")

// use ViewModel to maintain ui state - internal lateinit var mainTags: MutableList<String>
viewModel.mainTags = item.mainTags?.toMutableList() ?: mutableListOf("Hello")
loadTagsUi(mainTagAutoCompleteTextView, mainTagChipGroup, viewModel.mainTags, allTags)
private fun loadTagsUi(autoCompleteTextView: AutoCompleteTextView,
                       chipGroup: ChipGroup,
                       currentTags: MutableList<String>,
                       allTags: List<String>) {

    val adapter = ArrayAdapter<String>(this,
        android.R.layout.simple_dropdown_item_1line,
        allTags)
    autoCompleteTextView.setAdapter(adapter)

    fun addTag(name: String) {
        if (!name.isEmpty() && !currentTags.contains(name)) {
            addChipToGroup(name, chipGroup, currentTags)
            currentTags.add(name)
        }
        else {
            Timber.d("Invalid tag: $name")
        }
    }

    // select from auto complete
    autoCompleteTextView.setOnItemClickListener { adapterView, _, position, _ ->
        autoCompleteTextView.text = null
        val name = adapterView.getItemAtPosition(position) as String
        addTag(name)
    }

    // done keyboard button is pressed
    autoCompleteTextView.setOnEditorActionListener { textView, actionId, keyEvent ->
        if (actionId == EditorInfo.IME_ACTION_DONE) {
            val name = textView.text.toString()
            textView.text = null
            addTag(name)
            return@setOnEditorActionListener true
        }
        false
    }

    // space or comma is detected
    autoCompleteTextView.addTextChangedListener {
        if (it != null && it.isEmpty()) {
            return@addTextChangedListener
        }

        if (it?.last() == ',' || it?.last() == ' ' ) {
            val name = it.substring(0, it.length - 1)
            addTag(name)

            mainTagAutoCompleteTextView.text = null
            // mainTagAutoCompleteTextView.removeTextChangedListener(this)
        }
    }

    // initialize
    for (tag in currentTags) {
        addChipToGroup(tag,  mainTagChipGroup, currentTags)
    }
}

private fun addChipToGroup(name: String, chipGroup: ChipGroup, items: MutableList<String>) {
    val chip = Chip(context)
    chip.text = name

    chip.isClickable = true
    chip.isCheckable = false
    chip.isCloseIconVisible = true

    chipGroup.addView(chip)

    chip.setOnCloseIconClickListener {
        chipGroup.removeView(chip)
        items.remove(name)
    }
}

You might bump into java.lang.IllegalArgumentException: The style on this component requires your app theme to be Theme.MaterialComponents (or a descendant) error.

Edit res/values/styles and make sure AppTheme parent inherit from Theme.MaterialComponents.

<style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar">
    <!-- Customize your theme here. -->
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
</style>
This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License.