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.
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.
- By implementing the
TextWatcher
interface in the activity itself. The changes are highlighted below. Notice that the methods of theTextWatcher
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);
}
}
- 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.