Как использовать addTextChangeListener в Kotlin с лямбда-выражением?
Проблема с addTextChangedListener в Kotlin
Вы столкнулись с типичной ситуацией: addTextChangedListener ожидает объект интерфейса TextWatcher, который содержит три метода. В Java вы использовали анонимный класс, в Kotlin прямой лямбда-синтаксис не сработает, потому что интерфейс не является функциональным (не имеет единственного абстрактного метода).
Решение 1: Создайте extension-функцию (рекомендуется)
Самый элегантный способ — написать свою extension-функцию для EditText, которая принимает лямбду только для нужного колбэка. Например, для onTextChanged:
fun EditText.onTextChanged(onTextChanged: (CharSequence?, Int, Int, Int) -> Unit) {
this.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) {
onTextChanged(s, start, before, count)
}
override fun afterTextChanged(s: Editable?) {}
})
}
Теперь используйте так:
editText.onTextChanged { text, start, before, count ->
// ваша логика
println("Текст изменён: $text")
}
Вы можете создать аналогичные функции для beforeTextChanged и afterTextChanged по необходимости.
Решение 2: Используйте объектную анонимную реализацию с одним методом
Если не хотите писать extension, можно создать объект TextWatcher с помощью object : TextWatcher и передать его напрямую:
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?) {}
})
Это почти как в Java, но с ключевым словом object.
Решение 3: Используйте библиотеку Anko (устарело, но упоминается)
В старой библиотеке Anko были готовые лямбда-обёртки:
editText.addTextChangedListener {
onTextChanged { s, start, before, count -> /* логика */ }
}
Однако Anko сейчас не рекомендуется для новых проектов (депрекейтед). Лучше использовать первое решение.
Почему прямая лямбда не работает?
Ключевое отличие Kotlin от Java: лямбда-выражения можно передавать только функциональным интерфейсам (с одним абстрактным методом). TextWatcher имеет три метода, поэтому Kotlin не может автоматически преобразовать лямбду в объект этого интерфейса.
Бонус: Для afterTextChanged (только Editable)
Если вам нужен только afterTextChanged, создайте аналогичную extension:
fun EditText.afterTextChanged(afterTextChanged: (Editable?) -> Unit) {
this.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?) {
afterTextChanged(s)
}
})
}
Использование:
editText.afterTextChanged { editable ->
// работаем с Editable
}
Этот подход делает код чистым и переиспользуемым во всём проекте.
<<Альтернативный подход: Использование Kotlin-расширений из Android KTX
Если вы используете androidx.core:core-ktx, то в нём уже есть готовые extension-функции для работы с TextView. Например, функция doOnTextChanged:
// Добавьте зависимость в build.gradle:
// implementation "androidx.core:core-ktx:1.12.0"
editText.doOnTextChanged { text, start, before, count ->
// вызывается при каждом изменении текста
Log.d("TAG", "Новый текст: $text")
}
Также доступны doBeforeTextChanged и doAfterTextChanged. Это самый простой способ — не нужно писать свои обёртки, всё уже готово в официальной библиотеке.
Минус: Требуется зависимость
Если вы не используете AndroidX или не хотите подключать лишнюю библиотеку, тогда лучше написать свою extension-функцию, как в первом ответе. Однако для современных проектов KTX — стандарт де-факто.
Пример с afterTextChanged
editText.doAfterTextChanged { editable ->
// editable — это Editable? (может быть null)
editable?.let {
// обрабатываем
}
}
Этот подход избавляет вас от необходимости реализовывать пустые методы и делает код максимально лаконичным.