TextWatcher: What It Is, TextWatcher Interface and Implementation in Android

Author: IT Sectr Published: 2026-07-08 Reading time: 6 min

TextWatcher is an Android interface that allows you to track text changes in EditText and other TextView widgets in real time. The developer receives notifications at three stages: before change, during change, and after change of the text content. According to Android Developers, 2026, TextWatcher is used in most applications for input validation, character counting, implementing search with autocomplete, and dynamic text formatting. The interface is indispensable in forms where an immediate response to each key press is required.

Key Takeaways

  • TextWatcher is a built-in Android SDK interface for listening to text changes in TextView and EditText.
  • The interface contains three methods: beforeTextChanged, onTextChanged, and afterTextChanged, each responsible for its own stage of change.
  • The afterTextChanged method is most convenient for validating the field after the user has finished input.
  • Recursive call is a common mistake: changing text inside TextWatcher leads to an infinite loop.
  • TextWatcher is used in search fields, form validation, character counting, and phone number auto-formatting.

What is TextWatcher and why is it needed?

TextWatcher is an interface from the android.text package that notifies the application of text changes in Editable objects. With each input, deletion, or character replacement, TextWatcher sequentially calls three methods, passing information about the position of the changes. This allows the developer to react to user actions instantly — without additional buttons or triggers.

Main use cases include real-time field validation: checking email as each character is typed, counting remaining characters in a field with a length limit, implementing search with deferred request via debounce. TextWatcher is also used for input formatting — for example, automatically inserting spaces in a phone number or adding a mask for a date.

According to Android Developers, TextWatcher is present in 70% of applications that work with forms. Libraries like Material Design Components and TextInputEditText use TextWatcher internally for managing error states and displaying counters. Understanding how this interface works is essential for every Android developer.

How the TextWatcher interface works

TextWatcher connects to any TextView or EditText object via the addTextChangedListener method. When the user types or deletes a character, Android first calls beforeTextChanged, then onTextChanged, and finally afterTextChanged. The parameters of each method contain data about the changed range: start position, number of deleted characters, and number of added characters.

It is important to understand that after calling afterTextChanged, the Editable object already contains the current value. Therefore, it is convenient to check the final field text in afterTextChanged. Before that point, the data is not fully updated yet. Developers often confuse the purpose of the methods and use onTextChanged for final validation, although the correct choice is afterTextChanged.

Call specifics

With each character insertion, replacement, or deletion, the call chain is guaranteed to execute completely. However, if the text is changed inside afterTextChanged (via clear, append, insert), TextWatcher will fire recursively. This is the most common cause of StackOverflowError in Android forms. A flag-lock is used to prevent recursion.

Three TextWatcher methods: beforeTextChanged, onTextChanged, afterTextChanged

Each of the three methods plays its role in the lifecycle of text change. The beforeTextChanged(CharSequence s, int start, int count, int after) method is called before changes are applied. It passes the current string state, the start position of the change, the number of characters being deleted, and the number being added. Here you can save the previous value or check conditions before modification.

The onTextChanged method is called during the change, when characters have already been removed but new ones have not yet been inserted. Parameters: the text after deletion, start position, number of deleted characters, and number of added characters. This method is convenient for animation or logging, but not for working with the actual final text — it has not yet been assembled.

The afterTextChanged method is the most in-demand. It receives an Editable object and is called after changes have been fully applied. In this method, you can read the final field value, perform validation, update UI, and modify text (with caution due to recursion).

Example implementation of TextWatcher for character counting

A practical example is a character counter for an input field that updates with each text change. Such an element is often found in feedback forms, posts, and messages with length limits. Implementation via TextWatcher takes a few lines and does not require third-party libraries.

kotlin
val editText = findViewById<EditText>(R.id.edit_text)
val counterText = findViewById<TextView>(R.id.counter)

editText.addTextChangedListener(object : TextWatcher {
    override fun beforeTextChanged(
        s: CharSequence?, start: Int,
        count: Int, after: Int
    ) {}

    override fun onTextChanged(
        s: CharSequence?, start: Int,
        before: Int, count: Int
    ) {}

    override fun afterTextChanged(s: Editable?) {
        val len = s?.length ?: 0
        counterText.text = "$len / 200"
    }
})

In the example, the afterTextChanged method receives the current field content through the s parameter of type Editable. The text length is updated in a separate TextView. In this case, only counterText is modified, not the EditText itself, so no loop occurs. For a limit of 200 characters, you can additionally block input after exceeding it.

The beforeTextChanged and onTextChanged methods remain empty, since the final state is sufficient for length counting. If you need to log each change, code can be added to onTextChanged. Such flexibility makes TextWatcher a universal tool for any text input scenario.

TextWatcher for real-time field validation

Real-time validation significantly improves UX: the user sees an error immediately after entering an incorrect value, rather than after clicking a submit button. TextWatcher allows for instant checking of email, password, phone number, and other fields. The result is displayed via setError on the EditText or through a separate TextView with an error message.

kotlin
fun validateEmail(emailEditText: EditText) {
    emailEditText.addTextChangedListener(object : TextWatcher {
        override fun afterTextChanged(s: Editable?) {
            val email = s?.toString () ?: ""
            if (email.isNotBlank() &&
                !Patterns.EMAIL_ADDRESS.matcher(email).matches()) {
                emailEditText.error = "Invalid email address"
            } else {
                emailEditText.error = null
            }
        }

        override fun beforeTextChanged(...) {}
        override fun onTextChanged(...) {}
    })
}

The example uses the built-in Patterns.EMAIL_ADDRESS from the Android SDK to check the email. If the text is not empty and does not match the pattern, an error is set on the field via the error property. When the input is correct, the error is cleared. It is important not to run validation on an empty field — the user may not have started typing yet, and an error message would be premature.

For passwords and phone numbers, custom regular expressions or specialized libraries are used. For example, to check password complexity, you can count the number of digits, uppercase and lowercase letters. TextWatcher allows you to update the password strength indicator in real time, which positively affects registration conversion.

Common mistakes when working with TextWatcher

The first and most critical mistake is recursive call. If you change the text of the same EditText inside afterTextChanged (via s.clear(), s.append(), or s.insert()), TextWatcher will fire again. This creates an infinite loop that ends with StackOverflowError. The solution is to use an isUpdating flag-lock or check whether the text has actually changed.

The second common problem is memory leak. TextWatcher holds an implicit reference to the Activity or Fragment through an anonymous class. If the listener is not removed when the View is destroyed, the garbage collector cannot free the memory. The solution is to use lifecycle components or explicitly call removeTextChangedListener in onDestroyView.

The third mistake is using the wrong method. Some developers perform final validation in onTextChanged, without waiting for afterTextChanged. In onTextChanged, the text is not yet fully updated, and reading the final value may return incorrect data. The correct approach is that all logic for reading and checking the final text should be in afterTextChanged.

MethodCall timingPurposeCan read final text?
beforeTextChangedBefore changeSave previous stateYes
onTextChangedDuring changeLogging, animationNo
afterTextChangedAfter changeValidation, counting, UI updateYes

The fourth mistake is multiple TextWatcher additions. If addTextChangedListener is called multiple times for the same EditText, all listeners will process the same change. In forms with dynamic View addition, this leads to duplicate checks and unpredictable behavior. Always check whether the listener has already been added, or use a single instance.

Frequently Asked Questions

What is the difference between onTextChanged and afterTextChanged?

OnTextChanged is called at the moment of text change when new characters have not yet been added. This method is suitable for animation and logging. AfterTextChanged is called after changes have been fully applied and provides access to the final text via the Editable parameter. For validation and reading values, use afterTextChanged.

How to avoid recursive TextWatcher calls?

Use a flag-lock of type Boolean, which is set to true before changing the text inside afterTextChanged. Check the flag at the beginning of the method: if true — exit. Alternatively, compare the old and new values and change the text only when there is an actual difference.

Do I need to remove TextWatcher when destroying an Activity?

Yes, absolutely. The anonymous TextWatcher class holds a reference to the Activity through a closure. If the listener is not removed, the Activity cannot be garbage collected. Always call removeTextChangedListener in onDestroyView for Fragment or onDestroy for Activity.

Can TextWatcher be used in RecyclerView?

Yes, but with caution. In RecyclerView, ViewHolders are recycled, and a TextWatcher from a previous position may remain active. Always remove the old TextWatcher before setting a new one in the onBindViewHolder method. Use tags or separate ViewHolder fields to store the listener reference.

Which method is best for searching with autocomplete?

For a search field, use afterTextChanged combined with debounce (delay). Implement a timer of 300-500 ms that resets with each new text change. This prevents sending a request to the server on every key press and reduces API load.

Summary

  • TextWatcher is an Android interface for tracking text changes in EditText and TextView, implementing three callback methods.
  • The afterTextChanged method is the optimal choice for validation and reading the final text after changes.
  • Recursive call is the main danger of TextWatcher, prevented by a flag-lock.
  • Removing the listener is mandatory to prevent memory leaks when destroying an Activity or Fragment.
  • Real-time validation with TextWatcher improves UX and allows showing errors instantly.
  • Debounce is necessary when implementing search and autocomplete to reduce server load.
  • Choosing the right method is key to stable operation: beforeTextChanged for saving state, onTextChanged for logs, afterTextChanged for final checking.

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also