TextWatcher es una interfaz de Android que permite rastrear cambios de texto en EditText y otros TextView en tiempo real. El desarrollador recibe notificaciones en tres etapas: antes del cambio, durante el cambio y después del cambio del contenido textual. Según Android Developers, 2026, TextWatcher se utiliza en la mayoría de las aplicaciones para validación de entrada, conteo de caracteres, implementación de búsqueda con autocompletado y formato dinámico de texto. La interfaz es indispensable en formularios donde se requiere una reacción inmediata a cada pulsación de tecla.
Puntos clave
TextWatcher es una interfaz del paquete android.text que notifica a la aplicación sobre cambios de texto en objetos Editable. Con cada entrada, eliminación o reemplazo de caracteres, TextWatcher llama secuencialmente a tres métodos, transmitiendo información sobre la posición de los cambios. Esto permite al desarrollador reaccionar a las acciones del usuario al instante, sin botones adicionales ni disparadores.
Los principales casos de uso incluyen validación de campos en tiempo real: verificar el correo electrónico mientras se escribe cada carácter, contar los caracteres restantes en un campo con límite de longitud, implementar búsqueda con solicitud diferida mediante debounce. TextWatcher también se utiliza para formatear la entrada, por ejemplo, inserción automática de espacios en un número de teléfono o añadir una máscara para una fecha.
Según Android Developers, TextWatcher está presente en el 70% de las aplicaciones que trabajan con formularios. Bibliotecas como Material Design Components y TextInputEditText usan TextWatcher internamente para gestionar estados de error y mostrar contadores. Comprender el funcionamiento de esta interfaz es esencial para todo desarrollador Android.
TextWatcher se conecta a cualquier objeto TextView o EditText mediante el método addTextChangedListener. Cuando el usuario escribe o elimina un carácter, Android llama primero a beforeTextChanged, luego a onTextChanged y finalmente a afterTextChanged. Los parámetros de cada método contienen datos sobre el rango modificado: posición inicial, número de caracteres eliminados y número de caracteres añadidos.
Es importante entender que después de llamar a afterTextChanged, el objeto Editable ya contiene el valor actual. Por lo tanto, es conveniente verificar el texto final del campo en afterTextChanged. Antes de ese momento, los datos aún no están completamente actualizados. Los desarrolladores a menudo confunden el propósito de los métodos y usan onTextChanged para la validación final, aunque la elección correcta es afterTextChanged.
Con cada inserción, reemplazo o eliminación de caracteres, la cadena de llamadas se ejecuta completamente de forma garantizada. Sin embargo, si se cambia el texto dentro de afterTextChanged (mediante clear, append, insert), TextWatcher se activará recursivamente. Esta es la causa más común de StackOverflowError en formularios Android. Para evitar la recursión se utiliza un flag de bloqueo.
Cada uno de los tres métodos desempeña su papel en el ciclo de vida del cambio de texto. El método beforeTextChanged(CharSequence s, int start, int count, int after) se llama antes de aplicar los cambios. Transmite el estado actual de la cadena, la posición de inicio del cambio, el número de caracteres que se eliminan y el número que se añaden. Aquí se puede guardar el valor anterior o verificar condiciones antes de la modificación.
El método onTextChanged se llama durante el cambio, cuando los caracteres ya se han eliminado pero los nuevos aún no se han insertado. Parámetros: el texto después de la eliminación, posición inicial, número de caracteres eliminados y número de caracteres añadidos. Este método es conveniente para animación o registro, pero no para trabajar con el texto final real, ya que aún no está ensamblado.
El método afterTextChanged es el más demandado. Recibe un objeto Editable y se llama después de que los cambios se hayan aplicado completamente. En este método se puede leer el valor final del campo, realizar validación, actualizar la UI y modificar el texto (con precaución debido a la recursión).
Un ejemplo práctico es un contador de caracteres para un campo de entrada que se actualiza con cada cambio de texto. Este elemento se encuentra a menudo en formularios de comentarios, publicaciones y mensajes con límite de longitud. La implementación mediante TextWatcher requiere pocas líneas y no necesita bibliotecas de terceros.
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"
}
})
En el ejemplo, el método afterTextChanged recibe el contenido actual del campo a través del parámetro s de tipo Editable. La longitud del texto se actualiza en un TextView separado. En este caso, solo se modifica counterText, no el EditText en sí, por lo que no se produce ningún bucle. Para un límite de 200 caracteres, se puede bloquear la entrada adicional después de superarlo.
Los métodos beforeTextChanged y onTextChanged permanecen vacíos, ya que el estado final es suficiente para contar la longitud. Si se necesita registrar cada cambio, se puede añadir código en onTextChanged. Esta flexibilidad hace de TextWatcher una herramienta universal para cualquier escenario de entrada de texto.
La validación en tiempo real mejora significativamente la UX: el usuario ve un error inmediatamente después de introducir un valor incorrecto, en lugar de después de pulsar el botón de envío. TextWatcher permite la comprobación instantánea de correo electrónico, contraseña, número de teléfono y otros campos. El resultado se muestra mediante setError en el EditText o a través de un TextView separado con un mensaje de error.
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(...) {}
})
}
El ejemplo utiliza el Patterns.EMAIL_ADDRESS integrado del SDK de Android para verificar el correo electrónico. Si el texto no está vacío y no coincide con el patrón, se establece un error en el campo mediante la propiedad error. Cuando la entrada es correcta, el error se limpia. Es importante no ejecutar la validación en un campo vacío; el usuario puede no haber empezado a escribir todavía y un mensaje de error sería prematuro.
Para contraseñas y números de teléfono se utilizan expresiones regulares personalizadas o bibliotecas especializadas. Por ejemplo, para verificar la complejidad de una contraseña, se puede contar el número de dígitos, letras mayúsculas y minúsculas. TextWatcher permite actualizar el indicador de fortaleza de la contraseña en tiempo real, lo que afecta positivamente la conversión de registro.
El primer error y el más crítico es la llamada recursiva. Si se cambia el texto del mismo EditText dentro de afterTextChanged (mediante s.clear(), s.append() o s.insert()), TextWatcher se activará de nuevo. Esto crea un bucle infinito que termina en StackOverflowError. La solución es usar un flag de bloqueo isUpdating o comprobar si el texto realmente ha cambiado.
El segundo problema común es la fuga de memoria. TextWatcher mantiene una referencia implícita a la Activity o Fragment a través de una clase anónima. Si el listener no se elimina al destruir la Vista, el recolector de basura no puede liberar la memoria. La solución es usar componentes de ciclo de vida o llamar explícitamente a removeTextChangedListener en onDestroyView.
El tercer error es usar el método incorrecto. Algunos desarrolladores realizan la validación final en onTextChanged, sin esperar a afterTextChanged. En onTextChanged, el texto aún no está completamente actualizado y la lectura del valor final puede devolver datos incorrectos. El enfoque correcto es que toda la lógica de lectura y verificación del texto final debe estar en afterTextChanged.
| Método | Momento de llamada | Propósito | ¿Se puede leer el texto final? |
|---|---|---|---|
| beforeTextChanged | Antes del cambio | Guardar estado anterior | Sí |
| onTextChanged | Durante el cambio | Registro, animación | No |
| afterTextChanged | Después del cambio | Validación, conteo, actualización de UI | Sí |
El cuarto error es la adición múltiple de TextWatcher. Si se llama a addTextChangedListener varias veces para el mismo EditText, todos los listeners procesarán el mismo cambio. En formularios con adición dinámica de Vistas, esto provoca comprobaciones duplicadas y comportamiento impredecible. Verifique siempre si el listener ya se ha añadido, o utilice una única instancia.
Preguntas frecuentes
OnTextChanged se llama en el momento del cambio de texto cuando los nuevos caracteres aún no se han añadido. Este método es adecuado para animación y registro. AfterTextChanged se llama después de que los cambios se han aplicado completamente y proporciona acceso al texto final a través del parámetro Editable. Para validación y lectura de valores, use afterTextChanged.
Utilice un flag de bloqueo de tipo Boolean, que se establece en true antes de cambiar el texto dentro de afterTextChanged. Verifique el flag al inicio del método: si es true, salga. Alternativamente, compare los valores antiguos y nuevos y cambie el texto solo cuando haya una diferencia real.
Sí, absolutamente. La clase anónima de TextWatcher mantiene una referencia a la Activity a través de un cierre. Si el listener no se elimina, la Activity no puede ser recolectada por el recolector de basura. Siempre llame a removeTextChangedListener en onDestroyView para Fragment o en onDestroy para Activity.
Sí, pero con precaución. En RecyclerView, los ViewHolders se reutilizan y un TextWatcher de una posición anterior puede permanecer activo. Siempre elimine el TextWatcher antiguo antes de establecer uno nuevo en el método onBindViewHolder. Use etiquetas o campos separados del ViewHolder para almacenar la referencia al listener.
Para un campo de búsqueda, use afterTextChanged en combinación con debounce (retraso). Implemente un temporizador de 300-500 ms que se reinicia con cada nuevo cambio de texto. Esto evita enviar una solicitud al servidor en cada pulsación de tecla y reduce la carga de la API.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también