Android studio валидация kotlin

Easy EditText content validation with Kotlin

April 11, 2018 3 minute read

One of the things that I always deal with in my apps is the need to validate the input fields of a form. I can easily do this with this sample fragment of code (for example, for a length check):

The TextWatcher is used to detect changes in the EditText control — roughly akin to “if the text changes, do something”. I use EditText.setError() (well, the Kotlin equivalent) to alert the UI that there is an issue with the input field. Android renders this as part of the UI with an alert icon and a message box.

This is mildly horrible code and an artifact of the Java classes that are used underneath. I’ve got boilerplate code (from the TextWatcher class) and duplicated code (because I have to set the error initially as well as validate the text along the way).

What would an ideal form look like? How about something like this?

Instead of 10 lines of code, I only need to write 1 line of code plus bring in an extension function or two (which I can make part of my standard library of extension functions).

The afterTextChanged extension

I like to split this problem up. For instance, the addTextChangeListener piece is a useful function in itself. I have this in my standard library of Kotlin extensions:

Note the definition of the Kotlin lambda function afterTextChanged . It takes a string and doesn’t need a return value. This simplifies my original example to the following:

This is much improved already, but I still have duplicated code.

The validate extension

I can abstract this duplicate code out with another extension function:

My validator is a lambda (or function) that takes a string and returns a boolean. I can also deal with the duplication here so my main code-path is not duplicating the code.

I have one more useful extension function to check that a string is a valid email address:

Put these together, and I can do the following:

This is must more readable than the original 10–12 lines of code (depending on how much compression you do to avoid multiple lines). When you run an app with this code, you may see the following:

When the user types in a valid email address, the icon and message go away.

Should you always use extension functions to make your code more readable? Probably not. However, you will find a number of utility functions that you use repeatedly. These are candidates for your standard extensions. You can find more on GitHub (check out https://github.com/jitinsharma/Kotlin.someExtensions as an example)

Lambdas and extension functions are just two of the many reasons I love Kotlin!

Источник

Spring валидация входных DTO в Kotlin. Краткая инструкция для backend-разработчика

При переходе с Java на Kotlin многие вопросы приходится решать заново, а точнее по-другому. Два года назад мы начали социальный open source проект BrainUp, базируясь на Kotlin и Spring. Проект сейчас активно развивается, а мы узнаём на практике, что значит разрабатывать Kotlin-проект с нуля, какие удобства язык вносит в нашу жизнь, вместе с тем привнося свои вопросы, задачи, которые надо решать по-новому.

Читайте также:  Настройка apple роутера с android

Использование, а точнее не использование data-классов в качестве entity и почему. (напишу статью позже при возможности).

Выбор code style плагина. У нас используется ktlint, инструкция настройки описана в отдельной статье.

Выбор фреймворка тестирования. У нас используется Kotest.

Выбор библиотеки для мокирования. У нас выбрана Mockk.

Варианты использования Kotest и Mockk можно посмотреть у нас в проекте.

Организация валидации входных DTO (Data Transfer Object) с помощью Spring.

Настройка Sonar для Kotlin.

В этой статье расскажу про наш опыт организации валидации входных DTO с помощью Spring, с какими вопросами мы столкнулись в ходе реализации этой идеи в Kotlin и как их решали.

Итак, для добавления валидации в проект нужно пройти эти три шага:

1 шаг. Добавление аннотаций к полям в DTO

В Java мы пользовались такими аннотациями, как @NotNull, @NotEmpty, @NotBlank и др., например:

Но такой вариант, переписанный на Kotlin, работать не будет:

Kotlin рабочий самый простой вариант будет выглядеть так:

Теперь рассмотрим подробнее валидации полей разных типов на реальных примерах.

1.1 Валидации для полей String работает как ожидается, вот интересные примеры из нашего проекта:

1.2 Валидация для типов дат, например LocalDateTime, работает тоже как ожидается:

1.3 Валидация для типов Int, Long — тут несколько неочевидный трюк, потому что такой вариант работать не будет:

То есть отправляя такой json < "audiometryTaskId": null >в контроллер, мы не словим ожидаемую ошибку валидации, а увидим, что было проставлено в поле audiometryTaskId значение 0. Ищем на stackoverflow, да есть такое.

Рабочее решение выглядит несколько несуразно:

Здесь поле audiometryTaskId объявлено как nullable, но аннотация говорит об обратном. Для принития этого кода, необходимо иметь в голове фразу: «By making the field nullable, you’re allowing it to be constructed, so that the JSR 303 validation can run on the object. As validator doesn’t run until the object is constructed», — что означает для этих типов для валидации необходим объект, который сначала должен быть создан, т.е. сделать поля nullable для возможности создания:

И уже далее по созданному объекту будет произведена Spring-овая валидация, далее это значение в DTO можно спокойно использовать как не nullable:
audiometryHistoryRequest.audiometryTaskId!!

При вызове функции с audiometryTaskId=null, получим MethodArgumentNotValidException:

Stacktrace

Улучшить данный вариант можно добавив читабельное сообщение message (смотрите 3й шаг откуда это сообщение берется):

В этом случае defaultMessage будет заменён нашим, и можно будет увидеть именно определённое нами сообщение в response:

Controller response

2 шаг. Добавление аннотации @Validated в контроллер.

Добавление аннотации @Validated в контроллер перед DTO, которую необходимо проверить при вызове данного end-point.
Например:

3 шаг. Добавление файла с сообщениями об ошибках (опционально).

Добавление файла с сообщениями об ошибках errorMessages.properties в папку resources, если хотите вынести сообщения в одно место.

errorMessages.properties

На этом с валидацией всё, всем желаю удачи!

Источник

Правильная валидация в Android

О чём это?

Эта статья — обзор моей Android-библиотеки валидации, которая позволяет организовать сложную валидацию данных. В первую очередь библиотека рассчитана на проверку пользовательского ввода.

Часто в мобильных приложениях приходится делать различные экраны для ввода пользователем информации. Но так как пользователи не отличаются умом и сообразительностью — приходится проверять, что они там написали: запрещенные символы, максимальная длина, соответствие RegExp и так далее.

Первое, что приходит в голову для решения проблемы — прицепить регулярку прямо на EditText

Например, вот так:

Но а если теперь я хочу проверять поле по двум разным RegExp и выводить разные ошибки?

Ну тогда можно добавить второй слушатель:

Однако, теперь если в поле ввести строку «12345» то ошибки не будет: первый слушатель выставит ошибку на поле, потому что поле содержит цифры, а вот второй слушатель скроет ошибку, потому что по его мнению — поле правильное, не более 10 символов.

Читайте также:  Все человек паук android

И это только одна проблема. Дальше — веселее:

Как повесить на поле множество правил валидации, чтобы они правильно работали вместе?

Как добавлять или удалять правила?

Где хранить эту кучу валидаторов?

Как проверять любые типы данных, а не только строковые?

и многое другое.

Решение

Вся валидация должна происходить во ViewModel или Presenter, но не в UI слое. Задача UI — должным образом реагировать на результат проверки.

Не должно быть никаких специальных view-классов с поддержкой валидации. Валидацию можно привязать к любой view

Валидаторы должны поддерживать множество различных правил проверки, чтобы, например, каждое правило выдавало собственную ошибку.

Схематично всё выглядит примерно вот так:

Теперь рассмотрим её подробнее.

Condition — основа всего

В основе всей валидации лежит условие ( Condition ) — простейший интерфейс с одним методом validate .

interface Condition

Как это работает?

На изи! У Condition есть метод validate(data) , который проверит данные и вернёт результат ValidationResult . Внутри ValidationResult будет булевый результат проверки isValid и сообщение об ошибке, которое должно появляться если isValid == false

Сложений и умножение

Condition можно складывать и умножать. Сложение работает как аналог булевого ИЛИ, а умножение как аналог булевого И

ИЛИ

Conditon(true)

Conditon(false)

Conditon(true)

Conditon(false)

И

Conditon(true)

Conditon(false)

Conditon(true)

Conditon(false)

Точно так же склыдываются или умножаются ValidationResult

Validator — проверка по множеству условий

А что если надо проверять значение по множеству условий?

Как это работает?

Validator по-сути является Condition , только более прокаченный.

Внутри Validator находится множуство условий Set . В момент проверки значение проверяется по каждому из условий, формируется набор результатов валидации Set . Затем, этот набор с результатами передается на вход оператору ( Operator ), который и решает, какой будет финальный результат валидации. Вот, Всё.

Validator

У валидатора есть свои приколы:

Оператор

Operator — это просто Condition > , то есть тупа проверяет коллекцию результатов валидации. Получается такой аналог логического оператора из начального курса булевой алгебры. По-умолчанию используется оператор-конъюнкция.

Но можно написать свой оператор, который, например, будет выдавать ValidationResult(true) если количество валидных условий достигло порогового значения.

Нельзя удалять оператор! Validator не может работать без оператора

Наблюдение за изменением оператора

Может быть такое, что необходимо отслеживать изменения оператора. Например, чтобы обновить view.

Набор условий

Наблюдение за изменением условий

Чтобы следить за списком условий — добавьте слушателя OnConditionsChangedListener , который будет вызываться при любом изменении условий

Если нужно сделать много преобразований можно использовать changeConditionsSet , чтобы слушатель OnConditionsChangedListener сработал только один раз — после всех преобразований набора условий.

LiveDataValidator — реактивная валидация

Было бы удобно, если бы валидатор самостоятельно проверял данные при каждом их изменении. Так и сделаем! Сейчас модно молодежно использовать LiveData . Так пусть валидатор подпишется на неё и будет проверять каждое значение.

LiveDataValidator

LiveDataValidator работает так же как и обычный Vlidator , однако у него есть свои особенности:

Состояние (state)

Состояние это результат последней проверки. Представляет собой LiveData , поэтому за состоянием валидатора можно удобно следить. LiveDataValidator всегда в актуальном состоянии пока он подписан на источник ( Validator.observe ; Validator.observeForever )

Активация LiveDataValidator

LiveDataValidator начинает работать только тогда, когда хоть кто-нибудь подписан на него

Реакция на другие LiveData

LiveDataValidator умеет следить за другими LiveData и реагировать на их изменения

Для этого есть метод watchOn

В примере выше liveDataValidator следит за полем textMaxLength и как только значение textMaxLength меняется liveDataValidator принудительно валидируется

Для подобных случаев есть метод triggerOn , который запускает валидацию всякий раз когда изменяется дополнительный источник

Есть 2 текстовых поля: на одном пики точены, на другом х** д*ы вовсе не пики Задача, чтобы второе поле не содержало в себе текст первого поля

Как видно, secondValidator проверяет поле second , но при этом использует исползует first для проверки. Но что если first изменился? Тогда валидатор будет висеть в неактуальном состоянии до следующего изменения second . Поэтому валидатору нужно следить за first и при каждом его изменении принудительно выполнять проверку Делается это методом triggerOn(LiveData ) , который будет запускать валидатор при каждом изменении first

Читайте также:  Не работает точка доступа андроид что делать

Вместо triggerOn можно так же использовать watchOn и самостоятельно прописать нужное действие

MuxLiveDataValidator — объединяем валидаторы

А теперь, когда у нас есть куча полей с LiveDataValidator’ами надо каким-то образом опредилить общий результат валидации. Самый распространённый пример: если все поля на форме заполнены правильно — включаем кнопку «Далее».

Для этого есть MuxLiveDataValidator . Он подписывается на множество LiveDataValidator’ов и как только один из них изменяется — MuxLiveDataValidator собирает состояния ( ValidationResult ) всех LiveDataValidator’ов и отдаёт их на проверку оператору ( Operator ). Operator выдаёт окончательный результат.

Короче, MuxLiveDataValidator работает типа как мультиплексор. Отсюда и название.

MuxLiveDataValidator

Состояние (state)

Аналогично LiveDataValidator у MuxLiveDataValidator есть состояние

Состояние это LiveData в котором находится последний результат проверки.

Активация MuxLiveDataValidator

Тут как у LiveDataValidator — доступ только по подписке

Когда вы подписываетесь на MuxLiveDataValidator , то все его LiveDataValidator активируются, то есть подписка распространяется и на них (такой вот аналог семейной подписки у MediatorLiveData ). То есть если вы подписались на MuxLiveDataValidator , то не можно не подписываться на те LiveDataValidator , за которыми он следит.

Добавление валидатора

Добавить LiveDataValidator можно при создании MuxLiveDataValidator

Можно и после создания

Удаление валидатора

Ну тут типа ваще всё изян

Установка оператора

По-умолчанию MuxLiveDataValidator использует оператор-конъюнкцию. Чтобы поменять логику выдачи финального ValidationResult нужно установить другой оператор

Есть возможность следить за сменой оператора чтобы, например, очистить ошибку на view.

Подключение валидаторов к view

ConditionViewBinder

ConditionViewBinder базовый связыватель view и Condition

В момент вызова ConditionViewBinder.validate() достает из view данные для проверки абстрактным методом getValidationData() . Эти данные улетают в Condition , который проверит их и вернет ValidationResult . Затем этот ValidationResult передаётся абстрактному методу onValidationResult() в котором и происходит изменения view.

ConditionViewBinder

Таким образом можно привязать любой валидатор к любой view

ValidatorViewBinder

Предназначен для более удобной работы с Validator : следит за изменениями оператора и условий валидатора.

LiveDataValidatorViewBinder

LiveDataValidator — особый пациент. Для него свой binder, который:

сам подписывается/отписывается на LiveDataValidator ( чтобы активировать его)

getValidationData() берется не из view, а прямо из валидатора (из его source )

LiveDataValidatorViewBinder нужно активировать. Тут 2 способа:

Через конструктор. В конструктор передать LifeycleOwner

Просто вызвать attach

Готовые реализации

TextConditionViewBinder

Связывает простые Condition с TextView . Проверяет поле при каждом изменении текста в нём

TextViewLiveDataValidatorBinder

Тут то же самое, что и TextConditionViewBinder , но тут работаем с LiveDataValidator .

Примеры

Простая валидация

Во ViewModel делаем простейший Condition

Во фрагменте (или активити) применяем условие к текстовому полю

Сложная валидация

Допустим у нас есть 3 поля: поле для ввода цифр, поле для ввода букв и поле, которое указывает максимальную длину поля ввода цифр. О как! А ещё нужно выводить общее состояние валидации всей формы в отдельное текстовое поле!

Для начала объявим сами поля и валидаторы к ним во ViewModel]

Чтобы динамически менять условия валидации — лучше всего написать свой валидатор. Потому что для смены условий нужно хранить ссылки на эти самые условия, а это лучше сделать в отдельном классе

Теперь идём во фрагмент и подключаем всё это дело

Общие рекомендации по использованию

Все валидаторы должны находиться во ViewModel (ну или в Presenter) Не надо выносить логику валидирования во фрагменты, активности и вообще на view уровень.

По-возможности используйте LiveDataValidator . Он самый прокаченный. И вообще вся библиотека ради него написана была

Аккуратнее с множеством условий. Вы можете добавить на поле противоречащие друг другу условия и будет непонятно что!

Делайте свои реализации. Создавайте свои ConditionViewBinder ы, чтобы работать с кастомными view Создавайте свои валидаторы если вам нужна более сложная валидация

Источник

Оцените статью