Skip to content

Instantly share code, notes, and snippets.

@swanson
Last active May 14, 2019 23:02
Show Gist options
  • Save swanson/10695086 to your computer and use it in GitHub Desktop.
Save swanson/10695086 to your computer and use it in GitHub Desktop.
Tungsten: A simple, rock-for-brains validation mini-library for Android.
public class LoginActivity extends Activity {
@InjectView(R.id.login_form)
private LoginForm mLoginForm;
// SNIP
public void onLoginButtonPressed() {
LoginFormValidator validator = new LoginFormValidator();
validator.validate(mLoginForm.getUsername(),
mLoginForm.getPassword(),
mLoginForm.getPasswordConfirmation());
if (validator.hasErrors()) {
// Apply errors to each field
// EditText use setError()
// Other views are displayed in an error dialog or in a summary TextView
Tungsten.showErrors(this, validator);
} else {
// POST to API
}
}
}
public class LoginFormValidator extends BaseValidator {
public void validate(String username, String password, String passwordConfirmation) {
resetErrors();
if (isNullOrEmpty(username)) {
addError(R.id.email, R.string.error_field_required);
}
if (isNullOrEmpty(password)) {
addError(R.id.password, R.string.error_field_required);
}
if (!password.equals(passwordConfirmation)) {
addError(R.id.password_confirmation, R.string.error_password_does_not_match);
}
}
}
import org.junit.Test;
public class LoginFormValidatorTest {
private final LoginFormValidator validator = new LoginFormValidator();
@Test
public void testThatEmptyUsernameIsInvalid() {
validator.validate("", "pw", "pw");
assertThat(validator).hasError(R.id.email);
}
@Test
public void testThatEmptyPasswordIsInvalid() {
validator.validate("username", "", "pw");
assertThat(validator).hasError(R.id.password)
.withMessage(R.string.error_field_required);
}
@Test
public void testThatPasswordConfirmationMustMatch() {
validator.validate("username", "pw", "wp");
assertThat(validator).hasError(R.id.password);
assertThat(validator).hasError(R.id.password_confirmation);
}
@Test
public void testThatAllCorrectFieldsHasNoErrors() {
validator.validate("username", "pw", "pw");
assertThat(validator).hasNoErrors();
}
}
public class LoginForm extends BaseForm {
@InjectView(R.id.username)
private EditText mUsername;
@InjectView(R.id.password)
private EditText mPassword;
@InjectView(R.id.password_confirm)
private EditText mPasswordConfirmation;
public LoginForm(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onLayoutInflated(View view) {
ButterKnife.inject(this, view);
}
public String getUsername() {
return mUsername.getText().toString();
}
public String getPassword() {
return mPassword.getText().toString();
}
public String getPasswordConfirmation() {
return mPasswordConfirmation.getText().toString();
}
@Override
protected int getLayoutId() {
return R.layout.login_form;
}
}
@swanson
Copy link
Author

swanson commented Apr 15, 2014

Tungsten

A simple, rock-for-brains validation mini-library for Android.

Why?

I think there is value in separating Form models from activities and Validator objects from Form models. In the same spirit as ActiveRecord::Validations, we "link" errors to field based on the id - in Rails-land this is the #id of the HTML element, in Android-land this is the @+id of the XML element.

Because we only rely on Android's R class (for view Ids and string resources) to set errors, we can test Validators in pure jUnit tests (yah fast tests!) and rely on Tungsten.showErrors() to wire everything up in our activity.

Why not?

The downside is that Validator will only work with non-Android inputs (if you want to use jUnit, if you use Robolectric then it's good to go).

A Form model will probably have references to Android widgets (see example), so there is a bit of cruft in order to get the String values out of the EditTexts.

Why not use Android Saripaar?

I don't really like annotations for validation - it gets weird for non-trivial cases, especially if you need to combine multiple fields. I'd rather just write some simple Java code. Having to specify an order on everything is weird.

The ValidationListener interface seems Android-ish but overly complicated. Asynchronous stuff seems like API response handling rather than validation to me.

This is stupid.

I'm not in love with it either, but hey - just playing around with this idea!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment