Advertisement
  1. Code
  2. Android SDK

Programación reactiva de Kotlin para una pantalla de registro de Android

Scroll to top
Read Time: 15 min

() translation by (you can also view the original English article)

Programación reactiva de Kotlin para una pantalla de registro de AndroidRxJava 2.0 es una popular biblioteca de programación reactiva que ha ayudado a innumerables desarrolladores de Android a crear aplicaciones altamente receptivas, utilizando menos código y menos complejidad, especialmente cuando se trata de administrar múltiples hilos.

Si es uno de los muchos desarrolladores que han hecho el cambio a Kotlin, ¡no significa que deba renunciar a RxJava!

En la primera parte de esta serie, le mostré cómo pasar de la programación con RxJava 2.0 en Java a la programación con RxJava en Kotlin. También vimos cómo desterrar los estándares de sus proyectos al aprovechar las funciones de extensión de RxKotlin y el secreto para evitar el problema de conversión de SAM que muchos desarrolladores encuentran cuando comienzan a utilizar RxJava 2.0 con Kotlin.

En esta segunda entrega, nos concentraremos en cómo RxJava puede ayudar a resolver los problemas que encontrará en los proyectos reales de Android, mediante la creación de una aplicación reactiva de Android utilizando RxJava 2.0, RxAndroid y RxBinding.

¿Cómo puedo usar RxJava en proyectos del mundo real?

En nuestro artículo Programacion Reactiva con RxJava y RxKotlin, creamos algunos Observables y Observers simples que imprimen datos en el Logcat de Android Studio, pero esta no es la forma en que utilizará RxJava en el mundo real.

En este artículo, voy a mostrarte cómo usar RxJava para crear una pantalla que se usa en innumerables aplicaciones de Android: la clásica pantalla de registro.

A typical Sign Up screen that youll find in countless Android applicationsA typical Sign Up screen that youll find in countless Android applicationsA typical Sign Up screen that youll find in countless Android applications

Si su aplicación tiene algún tipo de experiencia de suscripción, normalmente tendrá reglas estrictas sobre el tipo de información que acepta. Por ejemplo, tal vez la contraseña deba exceder una cierta cantidad de caracteres, o la dirección de correo electrónico debe estar en un formato de correo electrónico válido.

Si bien puede verificar la entrada del usuario una vez que presiona el botón Sign Up, esta no es la mejor experiencia de usuario, ya que los deja abiertos para enviar información que claramente nunca será aceptada por su aplicación.

Es mucho mejor monitorear al usuario a medida que escribe, y luego avisarle tan pronto como quede claro que está ingresando información que no cumple con los requisitos de su aplicación. Al proporcionar este tipo de comentarios en vivo y en curso, le da al usuario la oportunidad de corregir sus errores antes de presionar ese botón Sign Up.

Si bien puede monitorear la actividad de los usuarios utilizando vanot Kotlin, podemos ofrecer esta funcionalidad utilizando mucho menos código al contar con la ayuda de RxJava, además de algunas otras bibliotecas relacionadas.

Creando la interfaz de usuario

Comencemos construyendo nuestra interfaz de usuario. Voy a agregar lo siguiente:

  • Dos EditTexts, donde el usuario puede ingresar su dirección de correo electrónico (enterEmail) y contraseña (enterPassword).
  • Dos contenedores TextInputLayout, que rodearán nuestro enterEmail y enterPassword EditTexts. Estos contenedores mostrarán una advertencia cada vez que el usuario ingrese una dirección de correo electrónico o una contraseña que no cumpla con los requisitos de nuestra aplicación.
  • Un botón de visibilidad de contraseña, que permite al usuario alternar entre enmascarar la contraseña y verla como texto sin formato.
  • Un botón Sign up. Para ayudar a mantener este ejemplo centrado en RxJava, no implementaré esta parte de la experiencia de registro, por lo que marcaré este botón como deshabilitado.

Aquí está mi diseño terminado:

1
<android.support.constraint.ConstraintLayout xmlns:android="https://schemas.android.com/apk/res/android"
2
   xmlns:app="http://schemas.android.com/apk/res-auto"
3
  xmlns:tools="http://schemas.android.com/tools"
4
  android:id="@+id/linearLayout"
5
  android:layout_width="match_parent"
6
  android:layout_height="match_parent"
7
  android:orientation="vertical">
8
9
  <TextView
10
      android:id="@+id/signUp"
11
      android:layout_width="wrap_content"
12
      android:layout_height="34dp"
13
      android:layout_marginTop="16dp"
14
      android:gravity="center"
15
      android:text="Sign up for an account"
16
      android:textColor="#D81B60"
17
      android:textSize="25sp"
18
      app:layout_constraintEnd_toEndOf="parent"
19
      app:layout_constraintStart_toStartOf="parent"
20
      app:layout_constraintTop_toTopOf="parent" />
21
22
  <android.support.design.widget.TextInputLayout
23
      android:id="@+id/emailError"
24
      android:layout_width="match_parent"
25
      android:layout_height="81dp"
26
      app:layout_constraintBottom_toTopOf="@+id/passwordError"
27
      app:layout_constraintTop_toBottomOf="@+id/signUp"
28
      app:layout_constraintVertical_bias="0.100000024"
29
      app:layout_constraintVertical_chainStyle="packed"
30
      tools:layout_editor_absoluteX="0dp">
31
32
      <EditText
33
          android:id="@+id/enterEmail"
34
          android:layout_width="match_parent"
35
          android:layout_height="wrap_content"
36
          android:hint="Email address"
37
          android:inputType="textEmailAddress" />
38
39
  </android.support.design.widget.TextInputLayout>
40
41
  <android.support.design.widget.TextInputLayout
42
      android:id="@+id/passwordError"
43
      android:layout_width="match_parent"
44
      android:layout_height="wrap_content"
45
      android:layout_marginEnd="10dp"
46
      app:layout_constraintBottom_toTopOf="@+id/buttonSignUp"
47
      app:layout_constraintStart_toStartOf="parent"
48
      app:layout_constraintTop_toBottomOf="@+id/emailError"
49
      app:passwordToggleEnabled="true">
50
51
      <EditText
52
          android:id="@+id/enterPassword"
53
          android:layout_width="392dp"
54
          android:layout_height="wrap_content"
55
          android:hint="Create your password"
56
          android:inputType="textPassword" />
57
58
  </android.support.design.widget.TextInputLayout>
59
60
  <Button
61
      android:id="@+id/buttonSignUp"
62
      android:layout_width="match_parent"
63
      android:layout_height="wrap_content"
64
      android:background="#0000FF"
65
      android:enabled="false"
66
      android:text="Sign Up"
67
      android:textColor="@android:color/white"
68
      app:layout_constraintBottom_toBottomOf="parent" />
69
70
</android.support.constraint.ConstraintLayout>

Si lo desea, puede copiar / pegar esto en su aplicación, o simplemente puede descargar el código fuente del proyecto desde nuestro repositorio de GitHub.

Creación de una experiencia de inicio de sesión reactiva con Kotlin

Ahora veamos cómo podemos usar RxJava, más algunas bibliotecas relacionadas, para monitorear las opiniones de los usuarios y proporcionar comentarios en tiempo real.

Abordaré la pantalla Sign Up en dos partes. En la primera sección, te mostraré cómo usar la biblioteca RxBinding para registrarte y responder a eventos de cambio de texto. En la segunda sección, crearemos algunas funciones de transformación que validan la entrada del usuario y luego mostraremos un mensaje de error cuando corresponda.

Cree un nuevo proyecto con la configuración de su elección, pero cuando se le solicite, asegúrese de seleccionar la casilla de verificación Include Kotlin Support.

Respondiendo a eventos de cambio de texto

En esta sección, implementaremos la siguiente funcionalidad:

  • Detectar cuando el usuario está escribiendo en el campo enterEmail.
  • Ignore todos los eventos de cambio de texto que ocurren dentro de un corto espacio de tiempo, ya que esto indica que el usuario todavía está escribiendo.
  • Realice una acción cuando el usuario deje de escribir. En nuestra aplicación terminada, aquí es donde validaremos la entrada del usuario, pero en esta sección solo mostraré un Toast.

1. RxBinding

RxBinding es una biblioteca que facilita la conversión de una amplia gama de eventos de IU en Observables, en cuyo punto puede tratarlos como cualquier otro flujo de datos RxJava.

Vamos a monitorear los eventos de cambio de texto, combinando el widget.RxTextView con el método afterTextChangeEvents, por ejemplo:

1
RxTextView.afterTextChangeEvents(enterEmail)

El problema con tratar los eventos de cambio de texto como flujos de datos es que inicialmente tanto enterEmail como enterPassword EditTexts estarán vacíos, y no queremos que nuestra aplicación reaccione a este estado vacío como si fuera la primera emisión de datos en el flujo. RxBinding resuelve este problema al proporcionar un método skipInitialValue(), que usaremos para indicar a cada observador que ignore el valor inicial de su flujo.

1
       RxTextView.afterTextChangeEvents(enterEmail)
2
               .skipInitialValue()

Miro la biblioteca RxBinding con mayor detalle en mi artículo de RxJava 2 para aplicaciones de Android.

2. Operador de .debounce() de RxJava

Para ofrecer la mejor experiencia al usuario, debemos mostrar cualquier contraseña o advertencia de correo electrónico relevante después de que el usuario haya terminado de escribir, pero antes de que presionen el botón Sign Up.

Sin RxJava, la identificación de esta estrecha ventana de tiempo normalmente nos requeriría implementar un Timer, pero en RxJava solo necesitamos aplicar el operador debounce() a nuestro flujo de datos.

Voy a utilizar el operador debounce() para filtrar todos los eventos de cambio de texto que ocurren en sucesión rápida, es decir, cuando el usuario aún está escribiendo. Aquí, ignoramos todos los eventos de cambio de texto que ocurren dentro de la misma ventana de 400 milisegundos:

1
       RxTextView.afterTextChangeEvents(enterEmail)
2
               .skipInitialValue()
3
               .debounce(400, TimeUnit.MILLISECONDS)

3. AndroidSchedulers.mainThread() de RxAndroid ()

El archivo AndroidSchedulers.mainThread de la biblioteca RxAndroid nos brinda una manera fácil de cambiar a la principal hebra de interfaz de usuario principal de Android.

Dado que solo es posible actualizar la interfaz de usuario de Android desde el hilo principal de la IU, debemos asegurarnos de estar en este hilo antes de intentar mostrar cualquier advertencia de correo electrónico o contraseña, y antes de mostrar nuestro Toast.

1
       RxTextView.afterTextChangeEvents(enterEmail)
2
               .skipInitialValue()
3
               .debounce(400, TimeUnit.MILLISECONDS)
4
               .observeOn(AndroidSchedulers.mainThread())

4. Suscribir

Para recibir los datos que emite enterEmail, debemos suscribirnos a ellos:

1
       RxTextView.afterTextChangeEvents(enterEmail)
2
               .skipInitialValue()
3
               .debounce(400, TimeUnit.MILLISECONDS)
4
               .observeOn(AndroidSchedulers.mainThread())
5
               .subscribe {

5. Mostrar Toast

Eventualmente, queremos que nuestra aplicación responda a los eventos de cambio de texto validando la entrada del usuario, pero para ayudar a mantener las cosas claras, en este punto simplemente voy a mostrar un Toast.

Su código debe verse algo como esto:

1
import android.support.v7.app.AppCompatActivity
2
import android.os.Bundle
3
import android.widget.Toast
4
import com.jakewharton.rxbinding2.widget.RxTextView
5
import kotlinx.android.synthetic.main.activity_main.*
6
import io.reactivex.android.schedulers.AndroidSchedulers
7
import java.util.concurrent.TimeUnit
8
9
class MainActivity : AppCompatActivity() {
10
11
   override fun onCreate(savedInstanceState: Bundle?) {
12
       super.onCreate(savedInstanceState)
13
       setContentView(R.layout.activity_main)
14
       
15
               RxTextView.afterTextChangeEvents(enterEmail)
16
               .skipInitialValue()
17
               .debounce(400, TimeUnit.MILLISECONDS)
18
               .observeOn(AndroidSchedulers.mainThread())
19
               .subscribe {
20
                   Toast.makeText(this, "400 milliseconds since last text change", Toast.LENGTH_SHORT).show()
21
                   
22
                   
23
               }
24
   }
25
}

6. Actualice sus dependencias

Dado que estamos utilizando algunas bibliotecas diferentes, debemos abrir el archivo build.gradle de nuestro proyecto y agregar RxJava, RxBinding y RxAndroid como dependencias de proyectos:

1
dependencies {
2
  implementation fileTree(dir: 'libs', include: ['*.jar'])
3
  implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
4
  implementation 'com.android.support:design:28.0.0-alpha1'
5
  implementation 'com.android.support:appcompat-v7:28.0.0-alpha1'
6
  implementation 'com.android.support.constraint:constraint-layout:1.1.0'
7
8
//Add the RxJava dependency// 
9
10
  implementation 'io.reactivex.rxjava2:rxjava:2.1.9'
11
12
//Add the RxAndroid dependency//
13
14
  implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
15
16
//Add the RxBinding dependency//
17
18
  implementation 'com.jakewharton.rxbinding2:rxbinding:2.1.1'
19
20
}

Puede probar esta parte de su proyecto instalándola en su teléfono o tableta física Android o dispositivo virtual Android (AVD). Seleccione enterEmail EditText y comience a escribir; Debe aparecer Toast cuando dejes de escribir.

Test your projects enterEmail EditTextTest your projects enterEmail EditTextTest your projects enterEmail EditText

Validación de la entrada del usuario con funciones de transformación

A continuación, debemos establecer algunas reglas básicas sobre el tipo de entrada que nuestra aplicación aceptará, y luego verificar la entrada del usuario con este criterio y mostrar un mensaje de error cuando corresponda.

Verificar el correo electrónico o la contraseña del usuario es un proceso de varios pasos, de modo que para que nuestro código sea más fácil de leer, voy a combinar todos estos pasos en su propia función de transformación.

Aquí está el inicio de la función de transformación validateEmail:

1
//Define an ObservableTransformer. Input and output must be a string//

2
3
   private val validateEmailAddress = ObservableTransformer<String, String> { observable ->
4
5
//Use flatMap to apply a function to every item emitted by the Observable//

6
7
       observable.flatMap {
8
9
//Trim any whitespace at the beginning and end of the user’s input//

10
11
           Observable.just(it).map { it.trim() }
12
13
//Check whether the input matches Android’s email pattern//

14
15
                   .filter {
16
                       Patterns.EMAIL_ADDRESS.matcher(it).matches()
17
18
                   }

En el código anterior, estamos usando el operador de filter() para filtrar la salida del Observable en función de si coincide con el patrón de Patterns.EMAIL_ADDRESS de Android.

En la siguiente parte de la función de transformación, debemos especificar qué sucede si la entrada no coincide con el patrón EMAIL_ADDRESS. De forma predeterminada, cada error irrecuperable activará una llamada a onError(), que termina la secuencia de datos. En lugar de finalizar la transmisión, queremos que nuestra aplicación muestre un mensaje de error, así que usaré onErrorResumeNext, que indica al observable que responda a un error al pasar el control a un nuevo observable, en lugar de invocar a onError(). Esto nos permite mostrar nuestro mensaje de error personalizado.

1
//If the user’s input doesn’t match the email pattern, then throw an error//

2
3
                   .singleOrError()
4
                   .onErrorResumeNext {
5
                       if (it is NoSuchElementException) {
6
                           Single.error(Exception("Please enter a valid email address"))
7
                       } else {
8
                           Single.error(it)
9
10
                       }
11
                   }
12
                   .toObservable()
13
       }
14
   }

El último paso es aplicar esta función de transformación al flujo de datos de correo electrónico, utilizando el operador .compose(). En este punto, su MainActivity.kt debería verse así:

1
import android.support.v7.app.AppCompatActivity
2
import android.os.Bundle
3
import android.util.Patterns
4
import io.reactivex.Observable
5
import io.reactivex.ObservableTransformer
6
import io.reactivex.Single
7
import io.reactivex.android.schedulers.AndroidSchedulers
8
import kotlinx.android.synthetic.main.activity_main.*
9
import java.util.concurrent.TimeUnit
10
import com.jakewharton.rxbinding2.widget.RxTextView
11
12
class MainActivity : AppCompatActivity() {
13
14
   override fun onCreate(savedInstanceState: Bundle?) {
15
       super.onCreate(savedInstanceState)
16
       setContentView(R.layout.activity_main)
17
18
19
       RxTextView.afterTextChangeEvents(enterEmail)
20
               .skipInitialValue()
21
               .map {
22
                   emailError.error = null
23
                   it.view().text.toString()
24
               }
25
               .debounce(400,
26
27
//Make sure we’re in Android’s main UI thread//

28
29
                       TimeUnit.MILLISECONDS).observeOn(AndroidSchedulers.mainThread())
30
               .compose(validateEmailAddress)
31
               .compose(retryWhenError {
32
                   passwordError.error = it.message
33
               })
34
               .subscribe()
35
}
36
37
//If the app encounters an error, then try again//

38
39
   private inline fun retryWhenError(crossinline onError: (ex: Throwable) -> Unit): ObservableTransformer<String, String> = ObservableTransformer { observable ->
40
       observable.retryWhen { errors ->
41
42
//Use the flatmap() operator to flatten all emissions into a single Observable//

43
44
           errors.flatMap {
45
               onError(it)
46
               Observable.just("")
47
           }
48
49
       }
50
   }
51
52
//Define an ObservableTransformer, where we’ll perform the email validation//

53
54
   private val validateEmailAddress = ObservableTransformer<String, String> { observable ->
55
       observable.flatMap {
56
           Observable.just(it).map { it.trim() }
57
58
//Check whether the user input matches Android’s email pattern//

59
60
                   .filter {
61
                       Patterns.EMAIL_ADDRESS.matcher(it).matches()
62
63
                   }
64
65
//If the user’s input doesn’t match the email pattern, then throw an error//

66
67
                   .singleOrError()
68
                   .onErrorResumeNext {
69
                       if (it is NoSuchElementException) {
70
                           Single.error(Exception("Please enter a valid email address"))
71
                       } else {
72
                           Single.error(it)
73
74
                       }
75
                   }
76
                   .toObservable()
77
       }
78
   }
79
80
81
}

Instale este proyecto en su dispositivo Android o AVD, y verá que la porción de correo electrónico de la pantalla de Sign Up ahora está verificando su entrada con éxito. Intenta ingresar cualquier cosa que no sea una dirección de correo electrónico, y la aplicación te advertirá que esta no es una entrada válida.

Enter anything other than a valid email address and the app will display a warning messageEnter anything other than a valid email address and the app will display a warning messageEnter anything other than a valid email address and the app will display a warning message

Enjuagar y repetir: verificar la contraseña del usuario

En este punto, tenemos un campo enterEmail en pleno funcionamiento, y la implementación de enterPassword es solo un caso de repetir los mismos pasos.

De hecho, la única diferencia importante es que nuestra función transformation validatePassword necesita verificar diferentes criterios. Voy a especificar que la entrada de contraseña del usuario debe tener al menos 7 caracteres de longitud:

1
                  .filter { it.length > 7 }

Después de repetir todos los pasos anteriores, el MainActivity.kt completo debe verse más o menos así:

1
import android.support.v7.app.AppCompatActivity
2
import android.os.Bundle
3
import android.util.Patterns
4
import io.reactivex.Observable
5
import io.reactivex.ObservableTransformer
6
import io.reactivex.Single
7
import io.reactivex.android.schedulers.AndroidSchedulers
8
import kotlinx.android.synthetic.main.activity_main.*
9
import java.util.concurrent.TimeUnit
10
import com.jakewharton.rxbinding2.widget.RxTextView
11
12
class MainActivity : AppCompatActivity() {
13
14
  override fun onCreate(savedInstanceState: Bundle?) {
15
      super.onCreate(savedInstanceState)
16
      setContentView(R.layout.activity_main)
17
18
//Respond to text change events in enterEmail//

19
20
      RxTextView.afterTextChangeEvents(enterEmail)
21
22
//Skip enterEmail’s initial, empty state//

23
24
              .skipInitialValue()
25
26
//Transform the data being emitted//              

27
            
28
              .map {
29
                      emailError.error = null
30
                     
31
//Convert the user input to a String//                      

32
                      
33
                  it.view().text.toString()
34
              }
35
36
//Ignore all emissions that occur within a 400 milliseconds timespan//

37
38
              .debounce(400,
39
40
//Make sure we’re in Android’s main UI thread//

41
42
TimeUnit.MILLISECONDS).observeOn(AndroidSchedulers.mainThread())
43
44
//Apply the validateEmailAddress transformation function//

45
46
              .compose(validateEmailAddress)
47
             
48
//Apply the retryWhenError transformation function//              

49
              
50
              .compose(retryWhenError {
51
                       emailError.error = it.message
52
              })
53
              .subscribe()
54
55
//Rinse and repeat for the enterPassword EditText//

56
57
      RxTextView.afterTextChangeEvents(enterPassword)
58
              .skipInitialValue()
59
              .map {
60
                          passwordError.error = null
61
                  it.view().text.toString()
62
              }
63
              .debounce(400, TimeUnit.MILLISECONDS).observeOn(AndroidSchedulers.mainThread())
64
              .compose(validatePassword)
65
              .compose(retryWhenError {
66
                          passwordError.error = it.message
67
              })
68
              .subscribe()
69
  }
70
71
//If the app encounters an error, then try again//

72
73
  private inline fun retryWhenError(crossinline onError: (ex: Throwable) -> Unit): ObservableTransformer<String, String> = ObservableTransformer { observable ->
74
      observable.retryWhen { errors ->
75
76
///Use the flatmap() operator to flatten all emissions into a single Observable//

77
78
          errors.flatMap {
79
              onError(it)
80
              Observable.just("")
81
          }
82
83
      }
84
  }
85
86
//Define our ObservableTransformer and specify that the input and output must be a string//

87
88
  private val validatePassword = ObservableTransformer<String, String> { observable ->
89
      observable.flatMap {
90
          Observable.just(it).map { it.trim() }
91
92
//Only allow passwords that are at least 7 characters long//

93
94
                  .filter { it.length > 7 }
95
96
//If the password is less than 7 characters, then throw an error//

97
98
                  .singleOrError()
99
100
//If an error occurs.....//

101
102
                  .onErrorResumeNext {
103
                      if (it is NoSuchElementException) {
104
                         
105
//Display the following message in the passwordError TextInputLayout//

106
107
                          Single.error(Exception("Your password must be 7 characters or more"))
108
109
                      } else {
110
                          Single.error(it)
111
                      }
112
                  }
113
                  .toObservable()
114
                  
115
116
      }
117
118
  }
119
120
//Define an ObservableTransformer, where we’ll perform the email validation//  

121
122
  private val validateEmailAddress = ObservableTransformer<String, String> { observable ->
123
      observable.flatMap {
124
          Observable.just(it).map { it.trim() }
125
126
//Check whether the user input matches Android’s email pattern//

127
128
                  .filter {
129
                      Patterns.EMAIL_ADDRESS.matcher(it).matches()
130
131
                  }
132
133
//If the user’s input doesn’t match the email pattern...//

134
135
                  .singleOrError()
136
                  .onErrorResumeNext {
137
                      if (it is NoSuchElementException) {
138
                         
139
////Display the following message in the emailError TextInputLayout//

140
141
                          Single.error(Exception("Please enter a valid email address"))
142
                      } else {
143
                          Single.error(it)
144
                      }
145
                  }
146
                  .toObservable()
147
      }
148
  }
149
150
  
151
  }

Instale este proyecto en su dispositivo Android o AVD, y experimente con tipear en los campos enterEmail y enterPassword. Si ingresa un valor que no cumple con los requisitos de la aplicación, entonces se mostrará el mensaje de advertencia correspondiente, sin que tenga que tocar el botón Sign Up.

Puede descargar este proyecto completo de GitHub.

Conclusión

En este artículo, analizamos cómo RxJava puede ayudar a resolver los problemas del mundo real que encontrará al desarrollar sus propias aplicaciones de Android, utilizando RxJava 2.0, RxBinding y RxAndroid para crear una pantalla Sign Up.

Para obtener más información general sobre la biblioteca RxJava, asegúrese de revisar nuestro artículo Comenzar con RxJava 2.0.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.