В процессе профессиональной деятельности приходится постоянно сталкиваться с валидацией полей ввода текста на экранах мобильных устройств. Чаще всего это пара-тройка экранов на приложение (SignIn, SignUp, Profile).
Ради этого подтягивать внешние зависимости представляется избыточным. Например, тот же Hibernate Validator добавляет порядка 8000 методов и 1 мб к весу финальной apk, что выглядит… избыточным )
Поиск какого-то удачного решения в интернетах не увенчался успехом, поэтому было принято решение напилить свое. Посовещавшись с коллегой (с iOS направления) пришли к идее лаконичного решения, которая была впоследствии реализована на каждой из платформ.
Реализацией этой идеи под Android и хочу поделиться. Но предупрежу сразу, что в угоду простоте восприятия идеи примеры использования будут приводится в крайне простом варианте, возможно даже наивном. Плюс, в текущей реализации идеи не используется Rx.
Итак. Начинается все с очень простого интерфейса:
Собственно, это все )
Дальше нам понадобится его реализация для компоновки однотипных атомарных валидаторов воедино:
В качестве примера можно привести валидацию email поля. Для этого создадим два атомарных валидатора:
И, собственно, вариант использования:
Также эта идея подходит и для валидации DataObject’ов с последующим выводом ошибки на экран. Например, создадим валидатор для DataObject’а User:
И вариант использования:
Варианты валидации могут быть самыми различными: от валидации по ивенту (по нажатию кнопки, например, как это показано в примерах выше), до динамической валидации (в момент ввода текста):
Равно как и стратегия показа ошибок тоже может совершенно различной: от фокуса на поле ошибки с показом текста ошибки и показом клавиатуры (предпочтительный на мой взгляд), до показа всех ошибок на экране разом (довольно часто встречается в андроид приложениях именно такой вариант). Рекомендации Google по показу ошибок можно посмотреть по ссылке.
По итогу получаем крайне простую и вместе с тем гибкую идею валидации полей и объектов данных, которая по-сути ни капли не утяжеляет проект и реализуется по щелчку пальцев. Как бэ профит )
Ради этого подтягивать внешние зависимости представляется избыточным. Например, тот же Hibernate Validator добавляет порядка 8000 методов и 1 мб к весу финальной apk, что выглядит… избыточным )
Поиск какого-то удачного решения в интернетах не увенчался успехом, поэтому было принято решение напилить свое. Посовещавшись с коллегой (с iOS направления) пришли к идее лаконичного решения, которая была впоследствии реализована на каждой из платформ.
Реализацией этой идеи под Android и хочу поделиться. Но предупрежу сразу, что в угоду простоте восприятия идеи примеры использования будут приводится в крайне простом варианте, возможно даже наивном. Плюс, в текущей реализации идеи не используется Rx.
Итак. Начинается все с очень простого интерфейса:
public interface Validator<T> { boolean isValid(T value); String getDescription(); }
Собственно, это все )
Дальше нам понадобится его реализация для компоновки однотипных атомарных валидаторов воедино:
public class ValidatorsComposer<T> implements Validator<T> { private final List<Validator<T>> validators; private String description; public ValidatorsComposer(Validator<T>... validators) { this.validators = Arrays.asList(validators); } @Override public boolean isValid(T value) { for (Validator<T> validator : validators) { if (!validator.isValid(value)) { description = validator.getDescription(); return false; } } return true; } @Override public String getDescription() { return description; } }
В качестве примера можно привести валидацию email поля. Для этого создадим два атомарных валидатора:
public class EmptyValidator implements Validator<String> { @Override public boolean isValid(String value) { return !TextUtils.isEmpty(value); } @Override public String getDescription() { return "Field must not be empty"; } }
public class EmailValidator implements Validator<String> { @Override public boolean isValid(String value) { return Patterns.EMAIL_ADDRESS.matcher(value).matches(); } @Override public String getDescription() { return "Email should be in \'a@a.com\' format"; } }
И, собственно, вариант использования:
final ValidatorsComposer<String> emailValidatorsComposer = new ValidatorsComposer<>(new EmptyValidator(), new EmailValidator()); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (emailValidatorsComposer.isValid(emailEditText.getText().toString())) { errorTextView.setText(null); } else { errorTextView.setText(emailValidatorsComposer.getDescription()); } } });
Также эта идея подходит и для валидации DataObject’ов с последующим выводом ошибки на экран. Например, создадим валидатор для DataObject’а User:
public class User { public final String name; public final Integer age; public final Gender gender; public enum Gender {MALE, FEMALE} public User(String name, Integer age, Gender gender) { this.name = name; this.age = age; this.gender = gender; } }
public class UserValidator implements Validator<User> { private String description; @Override public boolean isValid(User value) { if (value == null) { description = "User must not be null"; return false; } final String name = value.name; if (TextUtils.isEmpty(name)) { description = "User name must not be blank"; return false; } final Integer age = value.age; if (age == null) { description = "User age must not be blank"; return false; } else if (age < 0) { description = "User age must be above zero"; return false; } else if (age > 100) { description = "User age is to much"; return false; } final User.Gender gender = value.gender; if (gender == null) { description = "User gender must not be blank"; return false; } return true; } @Override public String getDescription() { return description; } }
И вариант использования:
final Validator<User> userValidator = new UserValidator(); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { User user = new User(null, 12, User.Gender.MALE); if (userValidator.isValid(user)) { errorTextView.setText(null); } else { errorTextView.setText(userValidator.getDescription()); } } });
Варианты валидации могут быть самыми различными: от валидации по ивенту (по нажатию кнопки, например, как это показано в примерах выше), до динамической валидации (в момент ввода текста):
emailEditText.addTextChangedListener(new SimpleTextWatcher() { @Override public void afterTextChanged(Editable s) { if (!emailValidatorsComposer.isValid(s.toString())) { errorTextView.setText(emailValidatorsComposer.getDescription()); } } });
Равно как и стратегия показа ошибок тоже может совершенно различной: от фокуса на поле ошибки с показом текста ошибки и показом клавиатуры (предпочтительный на мой взгляд), до показа всех ошибок на экране разом (довольно часто встречается в андроид приложениях именно такой вариант). Рекомендации Google по показу ошибок можно посмотреть по ссылке.
По итогу получаем крайне простую и вместе с тем гибкую идею валидации полей и объектов данных, которая по-сути ни капли не утяжеляет проект и реализуется по щелчку пальцев. Как бэ профит )
