How to Use Textwatcher in Android

The Android framework provides the EditText interface element to allow the entry and editing of text. There are some use cases where one might need to detect text changes as they are typed in the EditText and react accordingly. One use case could be showing auto suggest values. 

The TextWatcher interface can be used for listening in for changes in text in an EditText.

This interface has three abstract methods - 

  • afterTextChanged(Editable s)
  • beforeTextChanged(CharSequence s, int start, int count, int after)
  • onTextChanged(CharSequence s, int start, int before, int count)

Wiring the TextWatcher to the EditText

If you take a look at EditText’s documentation, you won’t see any obvious way to wire the TextWatcher to it. But since EditText is subclassed from TextView, we have access to the addTextChangedListener method. This method takes an instance of the TextWatcher as its only parameter.

Lets write some code now.

We are going to build a very simple app to suggest some fruits as the user types. Here is how its going to look and behave.

Gif of the project

Below is the markup for the activity. We are going to use just two elements, a EditText and a TextView. We could have used a ListView to display the fruits but that will only lengthen the code.

<?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=".MainActivity">

    <EditText
        android:id="@+id/my_edittext"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:hint="Type here to see some autosuggested fruits"
        android:inputType="text"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/my_fruits"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="8dp"
        android:background="#FFEB3B"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/my_edittext" />

</androidx.constraintlayout.widget.ConstraintLayout>

We create a private class that implements the TextWatcher interface. We want to take the string as they are entered and populate the TextView with all fruits that contains the string in the EditText.

private class MyTextWatcher implements TextWatcher {

    @Override
    public void beforeTextChanged(CharSequence s, int start,
            int count, int after) {
    }

    @Override
    public void onTextChanged(CharSequence s, int start, 
        int before, int count) {
    }

    @Override
    public void afterTextChanged(Editable s) {

        String x = "";
        //we clear the TextView and return when no text has been
        //entered.
        if (s.toString().length() == 0) {
            myFruits.setText(x);
            return;
        };

        for(int i = 0; i < fruits.length; i++){
            //uppercasing to make the comparision case insensitive
            if (fruits[i].toUpperCase()
                .contains(s.toString().toUpperCase())){
                x = x + fruits[i] 
                    + System.getProperty("line.separator");;
            }
        }

        myFruits.setText(x);
    }
}

We pass an instance of this class to the addTextChangedListener method.

MyTextWatcher tWatcher;
. . . . . 
. . . . .
tWatcher = new MyTextWatcher();
myEditText.addTextChangedListener(tWatcher);
. . . . . 
. . . . .

Here is the complete code of the activity.

package com.mundanecode.textwatcherdemo;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.widget.EditText;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    EditText myEditText;
    MyTextWatcher tWatcher;
    TextView myFruits;
    String[] fruits = {"Banana", "Apple", "Orange", "Guava", 
        "Strawberry", "Mango", "Pineapple" };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        myEditText = findViewById(R.id.my_edittext);

        tWatcher = new MyTextWatcher();
        myEditText.addTextChangedListener(tWatcher);

        myFruits = findViewById(R.id.my_fruits);

    }

    private class MyTextWatcher implements TextWatcher {

        @Override
        public void beforeTextChanged(CharSequence s, int start,
             int count, int after) {
        }

        @Override
        public void onTextChanged(CharSequence s, int start, 
            int before, int count) {
        }

        @Override
        public void afterTextChanged(Editable s) {

            String x = "";

            if (s.toString().length() == 0) {
                myFruits.setText(x);
                return;
            };

            for(int i = 0; i < fruits.length; i++){
                if (fruits[i].toUpperCase()
                    .contains(s.toString().toUpperCase())){
                    x = x + fruits[i] 
                        + System.getProperty("line.separator");;
                }
            }

            myFruits.setText(x);
        }
    }
}

We also have the option of writing the above code in two different ways.

  1. By implementing the TextWatcher interface in the activity itself. The changes are highlighted below. Notice that the methods of the TextWatcher are implemented in the activity itself and we eliminate the need to create a separate class. Changes are highlighted below.
public class MainActivity extends AppCompatActivity
    implements TextWatcher {
    EditText myEditText;
    //MyTextWatcher tWatcher;
    TextView myFruits;
    String[] fruits = {"Banana", "Apple", "Orange", "Guava", 
        "Strawberry", "Mango", "Pineapple" };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        myEditText = findViewById(R.id.my_edittext);

        //tWatcher = new MyTextWatcher();
        myEditText.addTextChangedListener(this);
        myFruits = findViewById(R.id.my_fruits);
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, 
        int count, int after) {
    }

    @Override
    public void onTextChanged(CharSequence s, int start, 
        int before, int count) {
    }

    @Override
    public void afterTextChanged(Editable s) {
        String x = "";

        if (s.toString().length() == 0) {
            myFruits.setText(x);
            return;
        };
        
        for(int i = 0; i < fruits.length; i++){
            if (fruits[i].toUpperCase()
                    .contains(s.toString().toUpperCase())){
                x = x + fruits[i] 
                    + System.getProperty("line.separator");
            }
        }

        myFruits.setText(x);
    }    
}
  1. Second way is to make the implementation of the TextWatcher interface anonymous. This method too reduces code. Changes are highlighted below.
public class MainActivity extends AppCompatActivity {
    EditText myEditText;    
    TextView myFruits;
    String[] fruits = {"Banana", "Apple", "Orange", "Guava", 
        "Strawberry", "Mango", "Pineapple" };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        myEditText = findViewById(R.id.my_edittext);
        
        myEditText.addTextChangedListener(new TextWatcher(){
            @Override
            public void beforeTextChanged(CharSequence s, int start, 
                int count, int after) {
            }

            @Override
            public void onTextChanged(CharSequence s, int start, 
                int before, int count) {
            }

            @Override
            public void afterTextChanged(Editable s) {
                String x = "";

                if (s.toString().length() == 0) {
                    myFruits.setText(x);
                    return;
                };
                
                for(int i = 0; i < fruits.length; i++){
                    if (fruits[i].toUpperCase()
                            .contains(s.toString().toUpperCase())){
                        x = x + fruits[i] 
                            + System.getProperty("line.separator");
                    }
                }

                myFruits.setText(x);
            } 
        });

        myFruits = findViewById(R.id.my_fruits);
    }       
}

That’s about it.

Code is available at github.