Advertisement
  1. Code
  2. Coding Fundamentals

Kotlin Reactive Programming for an Android Sign-Up Screen

Scroll to top

RxJava 2.0 is a popular reactive programming library that’s helped countless Android developers create highly responsive apps, using less code and less complexity, especially when it comes to managing multiple threads.

If you’re one of the many developers who’s made the switch to Kotlin, then it doesn’t mean you need to give up on RxJava! 

In the first part of this series, I showed you how to move from programming with RxJava 2.0 in Java to programming with RxJava in Kotlin. We also looked at how to banish boilerplate from your projects by taking advantage of RxKotlin’s extension functions, and the secret to dodging the SAM conversion problem that many developers encounter when they first start using RxJava 2.0 with Kotlin. 

In this second instalment, we’ll be concentrating on how RxJava can help solve the issues you’ll encounter in real-life Android projects, by creating a reactive Android application using RxJava 2.0, RxAndroid and RxBinding.

How Can I Use RxJava in Real-World Projects?

In our Reactive Programming with RxJava and RxKotlin article, we created some simple Observables and Observers that print data to Android Studio’s Logcat—but this isn’t how you’ll use RxJava in the real world.

In this article, I'm going to show you how to use RxJava to create a screen that’s used in countless Android applications: the classic Sign Up screen. 

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

If your app has any kind of sign-up experience, then it’ll typically have strict rules about the kind of information it accepts. For example, maybe the password needs to exceed a certain number of characters, or the email address must be in a valid email format.

While you could check the user’s input once they hit the Sign Up button, this isn’t the best user experience, as it leaves them open to submitting information that’s clearly never going to be accepted by your application.

It’s far better to monitor the user as they’re typing, and then give them a heads-up as soon as it becomes clear they’re entering information that doesn’t meet your app’s requirements. By providing this kind of live and ongoing feedback, you give the user the opportunity to correct their mistakes before hitting that Sign Up button.

While you could monitor user activity using vanilla Kotlin, we can deliver this functionality using much less code by enlisting the help of RxJava, plus a few other related libraries.

Creating the User Interface

Let’s start by building our user interface. I’m going to add the following:

  • Two EditTexts, where the user can enter their email address (enterEmail) and password (enterPassword).
  • Two TextInputLayout wrappers, which will surround our enterEmail and enterPassword EditTexts. These wrappers will display a warning whenever the user enters an email address or password that doesn’t meet our app’s requirements.
  • A password visibility button, which allows the user to toggle between masking the password and viewing it as plain text.
  • Sign up button. To help keep this example focused on RxJava, I won’t be implementing this part of the sign-up experience, so I’ll be marking this button as disabled.

Here’s my finished layout:

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>

You can copy/paste this into your app if you want, or you can just download the project source code from our GitHub repo.

Creating a Reactive Sign-in Experience With Kotlin

Now let’s look at how we can use RxJava, plus a few related libraries, to monitor user input and provide feedback in real time. 

I’ll be tackling the Sign Up screen in two parts. In the first section, I’ll show you how to use the RxBinding library to register and respond to text change events. In the second section, we’ll create some transformation functions that validate the user’s input, and then display an error message where appropriate.

Create a new project with the settings of your choice, but when prompted, make sure you select the Include Kotlin Support checkbox. 

Responding to Text Change Events

In this section, we’ll implement the following functionality: 

  • Detect when the user is typing in the enterEmail field.
  • Ignore all text change events that occur within a short space of time, as this indicates that the user is still typing. 
  • Perform an action when the user stops typing. In our finished app, this is where we’ll validate the user’s input, but in this section I’ll just be displaying a Toast

1. RxBinding 

RxBinding is a library that makes it easier to convert a wide range of UI events into Observables, at which point you can treat them just like any other RxJava data stream.

We’re going to monitor text change events, by combining RxBinding’s widget.RxTextView with the afterTextChangeEvents method, for example:

1
RxTextView.afterTextChangeEvents(enterEmail)

The problem with treating text change events as data streams is that initially both the enterEmail and enterPassword EditTexts will be empty, and we don’t want our app to react to this empty state as though it’s the first data emission in the stream. RxBinding solves this problem by providing a skipInitialValue() method, which we’ll use to instruct each Observer to ignore their stream’s initial value.

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

I look at the RxBinding library in greater detail in my RxJava 2 for Android Apps article.

2. RxJava’s .debounce() Operator

To deliver the best user experience, we need to display any relevant password or email warnings after the user has finished typing, but before they hit the Sign Up button.

Without RxJava, identifying this narrow window of time would typically require us to implement a Timer, but in RxJava we just need to apply the debounce() operator to our data stream.

I’m going to use the debounce() operator to filter out all text change events that happen in quick succession, i.e. when the user is still typing. Here, we’re ignoring all text change events that happen within the same 400-millisecond window:

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

3. RxAndroid’s AndroidSchedulers.mainThread()

The RxAndroid library’s AndroidSchedulers.mainThread gives us an easy way to switch to Android’s all-important main UI thread.

Since it’s only possible to update Android’s UI from the main UI thread, we need to make sure we’re in this thread before we attempt to display any email or password warnings, and before we display our Toast

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

4. Subscribe

To receive the data being emitted by enterEmail, we need to subscribe to it:

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

5. Display the Toast

Eventually, we want our application to respond to text change events by validating the user’s input, but to help keep things straightforward, at this point I’m simply going to display a Toast

Your code should look something like this:

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. Update Your Dependencies

Since we’re using a few different libraries, we need to open our project’s build.gradle file and add RxJava, RxBinding and RxAndroid as project dependencies: 

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
}

You can test this part of your project by installing it on your physical Android smartphone or tablet, or Android Virtual Device (AVD). Select the enterEmail EditText and start typing; a Toast should appear when you stop typing. 

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

Validating the User’s Input With Transformation Functions

Next, we need to lay down some ground rules about the kind of input our application will accept, and then check the user’s input against this criteria and display an error message where appropriate. 

Checking the user’s email or password is a multi-step process, so to make our code easier to read, I’m going to combine all of these steps into their own transformation function. 

Here’s the start of the validateEmail transformation function:

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
                   }

In the above code, we’re using the filter() operator to filter the Observable’s output based on whether it matches Android’s Patterns.EMAIL_ADDRESS pattern.

In the next part of the transformation function, we need to specify what happens if the input doesn’t match the EMAIL_ADDRESS pattern. By default, every unrecoverable error will trigger a call to onError(), which terminates the data stream. Instead of ending the stream, we want our application to display an error message, so I’m going to use onErrorResumeNext, which instructs the Observable to respond to an error by passing control to a new Observable, rather than invoking onError(). This allows us to display our custom error message.

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
   }

The final step is to apply this transformation function to the email data stream, using the .compose() operator. At this point, your MainActivity.kt should look something like this: 

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
}

Install this project on your Android device or AVD, and you’ll find that the email portion of the Sign Up screen is now checking your input successfully. Try entering anything other than an email address, and the app will warn you that this isn’t a valid input. 

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

Rinse and Repeat: Checking the User’s Password

At this point, we have a fully functioning enterEmail field—and implementing enterPassword is mostly just a case of repeating the same steps.

In fact, the only major difference is that our validatePassword transformation function needs to check for different criteria. I’m going to specify that the user’s password input must be at least 7 characters long: 

1
                  .filter { it.length > 7 }

After repeating all of the previous steps, the completed MainActivity.kt should look something like this: 

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
  }

Install this project on your Android device or AVD, and experiment with typing into the enterEmail and enterPassword fields. If you enter a value that doesn’t meet the app’s requirements, then it’ll display the corresponding warning message, without you having to tap the Sign Up button.

You can download this complete project from GitHub.

Conclusion

In this article, we looked at how RxJava can help solve the real-world problems you’ll encounter when developing your own Android applications, by using RxJava 2.0, RxBinding and RxAndroid to create a Sign Up screen. 

For more background information about the RxJava library, be sure to check out our Get Started With RxJava 2.0 article. 

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.