Developer

Pro tip: Unify your Android text watchers with this handy technique

When validating an entire form declaring and assigning each EditText widget on your Android layout, here's a way to declare a single private class that can handle validation for each field in your form.

1024px-logoandroid051614.png

In the past, we've seen just how useful Android's TextWatcher interface can be. Not that long ago, I shared an algorithm for using a TextWatcher to parse date entry in real-time. Despite the strengths of Android's TextWatcher though, when you find yourself validating an entire form declaring and assigning each EditText widget on your layout, its own watcher can get tedious and hard to read.

In these instances I like to declare a single private class that can handle validation for each field in my form — particularly when multiple fields will use the same validation mechanism. The example below demonstrates this technique. Feel free to follow along with the step-by-step tutorial, or download an import the entire project directly into Eclipse.

1. Create a new Android project in Eclipse. Target Android SDK 14 (ICS) or better.

2. In the /res/layout folder, modify the activity_main.xml file to include three EditText widgets wrapped in a LinearLayout. Note that we use the "inputType" attribute to restrict data entry to numeric keys only.

activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="center"
    android:gravity="center"
    android:orientation="vertical" >

    <EditText
        android:id="@+id/edit_text_1"
        android:inputType="number"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:hint="enter any 3 digits" />
    
     <EditText
        android:id="@+id/edit_text_2"
        android:inputType="number"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:hint="enter any 3 digits" />
     
      <EditText
        android:id="@+id/edit_text_3"
        android:inputType="number"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:hint="first digit must be 9" />

</LinearLayout>

3. Next we move on to the /src/MainActivity.java file. The onCreate override is responsible for creating and assigning the NumberValidator to the EditText widgets. The NumberValidator class holds a weak reference to the UI widget and performs specific validation based on the ID.

MainActivity.java
package com.authorwjf.textwatcherdemo;

import java.lang.ref.WeakReference;

import android.app.Activity;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.widget.EditText;

public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		((EditText)findViewById(R.id.edit_text_1))
			.addTextChangedListener(new NumberValidator((EditText)findViewById(R.id.edit_text_1)));
		((EditText)findViewById(R.id.edit_text_2))
			.addTextChangedListener(new NumberValidator((EditText)findViewById(R.id.edit_text_2)));
		((EditText)findViewById(R.id.edit_text_3))
			.addTextChangedListener(new NumberValidator((EditText)findViewById(R.id.edit_text_3)));
	}

	private class NumberValidator implements TextWatcher {
		
		private WeakReference<EditText> mEditText;
		
		public NumberValidator(EditText et) {
			mEditText = new WeakReference<EditText>(et);
		}

		@Override
		public void beforeTextChanged(CharSequence s, int start, int count, int after) {
			// TODO Auto-generated method stub
		}

		@Override
		public void onTextChanged(CharSequence s, int start, int before, int count) {
			// TODO Auto-generated method stub
		}

		@Override
		public void afterTextChanged(Editable s) {
			final EditText et = mEditText.get();
			if (et!=null) {
				switch(et.getId()) {
				case R.id.edit_text_1:
				case R.id.edit_text_2:
					if (et.getText().length()!=3) {
						et.setError("Invalid Length");
					} else {
						et.setError(null);
					}
					break;
				case R.id.edit_text_3:
					if (et.getText().length()>0) {
						if (et.getText().charAt(0)!='9') {
							et.setError("Invalid First Digit");
						} else {
							et.setError(null);
						}
					} else {
						et.setError("Invalid Length");
					}
					break;
				}
			}
		}
		
	}
	
}

You can load the demo application to a device or an emulator and give it a try (Figure A).

Figure A

androidtextwatcher090414.png

As the hint for the various fields implies, the only rule for the first two fields is that the entry must be exactly three digits in length. The third field can be any string of numeric digits as long as the first is a 9. These validation rules are simplistic and arbitrary, but it should be clear how you could easily expand the case statement to encompass all your form's validation needs.

Also see

About William J. Francis

William J Francis began programming computers at age eleven. Specializing in embedded and mobile platforms, he has more than 20 years of professional software engineering under his belt, including a four year stint in the US Army's Military Intellige...

Editor's Picks

Free Newsletters, In your Inbox