TextWatcher — це інтерфейс Android, який дозволяє відстежувати зміни тексту в EditText та інших TextView у реальному часі. Розробник отримує сповіщення на трьох етапах: перед зміною, під час зміни та після зміни текстового вмісту. За даними Android Developers, 2026, TextWatcher застосовується в більшості додатків для валідації введення, підрахунку символів, реалізації пошуку з автодоповненням та динамічного форматування тексту. Інтерфейс незамінний у формах, де потрібна негайна реакція на кожне натискання клавіші.
Головне
TextWatcher — це інтерфейс із пакета android.text, який сповіщає додаток про зміни тексту в об'єктах Editable. При кожному введенні, видаленні або заміні символу TextWatcher послідовно викликає три методи, передаючи інформацію про положення змін. Це дозволяє розробнику реагувати на дії користувача миттєво — без додаткових кнопок або тригерів.
Основні сценарії використання включають валідацію полів у реальному часі: перевірка email при введенні кожного символу, підрахунок символів, що залишилися в полі з обмеженням довжини, реалізація пошуку з відкладеним відправленням запиту через debounce. Також TextWatcher застосовується для форматування введення — наприклад, автоматичне розставлення пробілів у номері телефону або додавання маски для дати.
За даними Android Developers, TextWatcher присутній у 70% додатків, що працюють з формами. Бібліотеки на кшталт Material Design Components і TextInputEditText використовують TextWatcher внутрішньо для керування станом помилки та відображення лічильників. Розуміння роботи цього інтерфейсу необхідне кожному Android-розробнику.
TextWatcher під'єднується до будь-якого об'єкта TextView або EditText через метод addTextChangedListener. Коли користувач вводить або видаляє символ, Android викликає спочатку beforeTextChanged, потім onTextChanged і нарешті afterTextChanged. У параметрах кожного методу передаються дані про змінюваний діапазон: стартова позиція, кількість видалених символів та кількість доданих символів.
Важливо розуміти, що після виклику afterTextChanged об'єкт Editable вже містить актуальне значення. Тому саме в afterTextChanged зручно перевіряти підсумковий текст поля. До цього моменту дані ще не повністю оновлені. Розробники часто плутають призначення методів і використовують onTextChanged для фінальної валідації, хоча правильний вибір — afterTextChanged.
При кожному вставленні, заміні або видаленні символу ланцюжок викликів гарантовано виконується повністю. Однак якщо всередині afterTextChanged змінити текст (через clear, append, insert), TextWatcher спрацює рекурсивно. Це найчастіша причина StackOverflowError у формах Android. Для запобігання рекурсії використовують прапорець-блокування.
Кожен із трьох методів виконує свою роль у життєвому циклі зміни тексту. Метод beforeTextChanged(CharSequence s, int start, int count, int after) викликається перед застосуванням змін. Він передає поточний стан рядка, позицію початку зміни, кількість видалених символів і кількість доданих. Тут можна зберегти попереднє значення або перевірити умови до модифікації.
Метод onTextChanged викликається в процесі зміни, коли символи вже видалені, але нові ще не вставлені. Параметри: текст після видалення, стартова позиція, кількість видалених символів і кількість доданих. Цей метод зручний для анімації або логування, але не для роботи з актуальним фінальним текстом — він ще не зібраний.
Метод afterTextChanged — найбільш затребуваний. Він отримує об'єкт Editable і викликається після того, як зміни повністю застосовані. У цьому методі можна читати підсумкове значення поля, виконувати валідацію, оновлювати UI та змінювати текст (з обережністю через рекурсію).
Практичний приклад — лічильник символів для поля введення, який оновлюється при кожній зміні тексту. Такий елемент часто зустрічається в формах зворотного зв'язку, постах і повідомленнях з обмеженням довжини. Реалізація через TextWatcher займає кілька рядків і не потребує сторонніх бібліотек.
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"
}
})
У прикладі метод afterTextChanged отримує поточний вміст поля через параметр s типу Editable. Довжина тексту оновлюється в окремому TextView. Для уникнення рекурсії в даному випадку змінюється лише counterText, а не сам EditText, тому цикл не виникає. При ліміті в 200 символів можна додатково блокувати введення після перевищення.
Методи beforeTextChanged і onTextChanged залишаються порожніми, оскільки для підрахунку довжини достатньо фінального стану. Якщо потрібно логувати кожну зміну, код можна додати в onTextChanged. Така гнучкість робить TextWatcher універсальним інструментом для будь-яких сценаріїв роботи з текстовим введенням.
Валідація в реальному часі суттєво покращує UX: користувач бачить помилку відразу після введення некоректного значення, а не після натискання кнопки відправлення. TextWatcher дозволяє реалізувати миттєву перевірку email, пароля, номера телефону та інших полів. Результат відображається через setError у EditText або через окремий TextView з повідомленням про помилку.
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(...) {}
})
}
У прикладі використовується вбудований Patterns.EMAIL_ADDRESS з Android SDK для перевірки email. Якщо текст не порожній і не відповідає шаблону, полю встановлюється помилка через властивість error. При коректному введенні помилка очищається. Важно не запускати валідацію на порожньому полі — користувач може ще не почати введення, і повідомлення про помилку буде передчасним.
Для паролів і номерів телефонів використовують кастомні регулярні вирази або спеціалізовані бібліотеки. Наприклад, для перевірки складності пароля можна підрахувати кількість цифр, літер у верхньому та нижньому регістрі. TextWatcher дозволяє оновлювати індикатор складності пароля в реальному часі, що позитивно впливає на конверсію реєстрації.
Перша і найкритичніша помилка — рекурсивний виклик. Якщо всередині afterTextChanged змінити текст того ж EditText (через s.clear(), s.append() або s.insert()), TextWatcher спрацює повторно. Це створює нескінченний цикл, який закінчується StackOverflowError. Рішення — використовувати прапорець-блокування isUpdating або перевіряти, чи змінився текст насправді.
Друга поширена проблема — витік пам'яті. TextWatcher містить неявне посилання на Activity або Fragment через анонімний клас. Якщо не видаляти listener при знищенні View, збирач сміття не зможе звільнити пам'ять. Рішення — використовувати lifecycle-компоненти або явно викликати removeTextChangedListener в onDestroyView.
Третя помилка — використання неправильного методу. Деякі розробники виконують фінальну валідацію в onTextChanged, не дочекавшись afterTextChanged. У onTextChanged текст ще не повністю оновлений, і читання підсумкового значення може повернути некоректні дані. Правильний підхід — вся логіка читання та перевірки фінального тексту повинна бути в afterTextChanged.
| Метод | Момент виклику | Призначення | Чи можна читати підсумковий текст? |
|---|---|---|---|
| beforeTextChanged | До зміни | Збереження попереднього стану | Так |
| onTextChanged | Під час зміни | Логування, анімація | Ні |
| afterTextChanged | Після зміни | Валідація, підрахунок, оновлення UI | Так |
Четверта помилка — множинне додавання TextWatcher. Якщо addTextChangedListener викликано кілька разів для одного EditText, усі listener будуть обробляти ту саму зміну. У формах з динамічним додаванням View це призводить до дублювання перевірок і непередбачуваної поведінки. Завжди перевіряйте, чи listener вже додано, або використовуйте єдиний екземпляр.
Часто задавані питання
OnTextChanged викликається в момент зміни тексту, коли нові символи ще не додані. Цей метод підходить для анімації та логування. AfterTextChanged викликається після повного застосування змін і дає доступ до підсумкового тексту через параметр Editable. Для валідації та читання значення використовуйте afterTextChanged.
Використовуйте прапорець-блокування типу Boolean, який встановлюється в true перед зміною тексту всередині afterTextChanged. На початку методу перевіряйте прапорець: якщо true — виходьте. Альтернативно можна порівнювати старе та нове значення і змінювати текст лише при реальному розходженні.
Так, обов'язково. Анонімний клас TextWatcher утримує посилання на Activity через замикання. Якщо listener не видалено, Activity не може бути зібрана збирачем сміття. Завжди викликайте removeTextChangedListener в onDestroyView для Fragment або onDestroy для Activity.
Так, але з обережністю. У RecyclerView ViewHolder перевикористовуються, і TextWatcher із попередньої позиції може залишитися активним. Завжди видаляйте старий TextWatcher перед встановленням нового в методі onBindViewHolder. Використовуйте теги або окремі поля ViewHolder для зберігання посилання на listener.
Для пошукового поля використовуйте afterTextChanged у поєднанні з debounce (затримкою). Реалізуйте таймер на 300-500 мс, який скидається при кожній новій зміні тексту. Це запобігає відправленню запиту на сервер при кожному натисканні клавіші та знижує навантаження на API.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також