Building simple and effective login forms on Android
This article describes a simple and effective way to achieve fancy “form validation” in Android, using custom View classes, custom drawable states and Android’s support library. The goal is to create one custom View class, that shows the correct UI depending on whether the View is in a (custom defined) error state. That UI can be set through drawables, and hence makes the view easily reusable and configurable in different AppThemes.
Suppose a form for signing up for an account with the following requirements:
- A field to fill in an email address
- A field to fill in a password
- A password visibility toggle that toggles the password visibility
- Furthermore we want our fields to have an error state that is activated when the input doesn’t conform with email- and password validation respectively
All of this logic can easily be put in the Presenter controlling this specific screen, but that doesn’t make it very reusable. We want to achieve this through 100% View-related logic: View classes and StateListDrawables
.
Custom StateListDrawables
StateListDrawables are XML files that contain a number of graphic images for different states of the drawable. A common example of this is an “activated” and “deactivated” state of a button. The StateListDrawable
would for example be:
<selector xmlns:android=”http://schemas.android.com/apk/res/android">
<item android:drawable=”@color/blue” android:state_enabled=”false” />
<item android:drawable=”@color/red”/>
</selector>
If this drawable is assigned to a Button
(through android:background=”@drawable/bg_button”
), then the button will be red while being in activated state (myButton.enabled = true
), and blue while being in deactivated state (myButton.enabled = false
). Simple, right?
The same logic can obviously be used for EditText
, like in our form. However, there is no XML attribute that describes an EditText
’s error-state, and so we have to create our own. We do this by adding our own stylable to attrs.xml
:
<resources>
<declare-styleable name="EditTextErrorState">
<attr name="state_error" format="boolean"/>
</declare-styleable>
</resources>
Now we can create a StateListDrawable that has has a different graphic image for different state_error
values.
bg_edittext.xml
:
<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:drawable="@drawable/grey"
app:state_error="false" /> <item android:drawable="@drawable/red_transparent"
app:state_error="true"/>
</selector>
textcolor_edittext.xml
:
<selector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:color="@color/charcoal_grey"
app:state_error="false" />
<item android:color="@color/red"
app:state_error="true"/>
</selector>
Custom EditText classes for email validation
Next up, we want to incorporate this state-logic into a View class, so that we can set the state of a View and show the right respective drawable is shown.
Let’s start with the email field. We want this field to be in a non-error state when it’s in focus (when the user is typing text into it). When it loses focus, we validate the text filled in by the user. If the email is invalid, we set the EditText
to it’s error state. If not, we set it to it’s non-error state. As we don’t want the field to become in an error state straight away, don’t update the error state if the input hasn’t been changed by user at least once.
Our (rather simple) email validator looks like this:
Which we use in our custom EmailValidationEditText
View class as follows:
Let’s go through this code step by step.
- Overriding
onCreateDrawableState()
allows us to inject our own states, which then become accessible by the drawables used in the view. - We attach an
onTextChangedListener
to the view, which sets a global boolean totrue
, in order to make sure that from now on this field will get validated and updated to a potential error state. - We attach an
onFocusChangeListener
to the view. Every time the view loses focus, we verify whether the input is valid, and refresh the drawable state.
Now all that’s left is to use this EditText
in our Activity, and style it accordingly!
<com.oddhov.signupformvalidation.tools.view.EmailValidationEditText
android:id="@+id/emailAddress"
style="@style/SignupEditText"
android:layout_width="match_parent"
android:layout_height="48dp"
android:hint="@string/signup_email_hint"/><style name="SignupEditText" parent="Widget.AppCompat.EditText">
<item name="colorControlNormal">@color/transparent</item>
<item name="colorControlActivated">@color/transparent</item>
<item name="colorControlHighlight">@color/transparent</item>
<item name="android:paddingLeft">@dimen/d2</item>
<item name="android:gravity">center_vertical</item>
<item name="android:textColorHint">@color/light_grey</item>
<item name="android:textSize">@dimen/s16</item>
<item name="android:inputType">
textEmailAddress|textNoSuggestions</item>
<item name="android:textColor">
@drawable/textColor_editText</item>
<item name="android:background">@drawable/bg_editText</item>
</style>
Note here the textColor
and background
attributes. These are the drawables are responsible for changing the styling of the EditText
, based on whether it is in error state or not.
Password validation
The View class used for password input and validation is generally the same as for the email. We want to make the password input visible and invisible, through a toggle. Android provides this through their support library, yay! 🎉
Meet TextInputLayout
, and TextInputEditText
. There are several excellent Medium posts out there that take you through the workings of these components (like this one), so I will skip that part. I just want to show you how you can easily apply the previously described logic to these components as well.
Our simple password validator:
And custom PasswordValidationTextInputEditText
class: