AndroidFormEnhancer

Form validation library for Android applications.

APACHE-2.0 License

Stars
74

AndroidFormEnhancer

AndroidFormEnhancerは、Androidアプリケーションで入力フォームを簡単に実装するためのライブラリです。 アノテーションを利用して、入力フォームに関する定義を簡潔に記述することができ、 ActivityやFragmentの中に含まれる画面との値のやり取りや入力チェックのコードを削減することができます。

主な機能

OK UI部品の入力値がシンプルなPOJOクラスで処理可能 OK 入力値検証がアノテーションで実装可能 OK 多数の入力値検証パターンが利用できカスタマイズも可能 OK 入力エラーメッセージは簡単に取得できダイアログ表示も可能 OK エラーメッセージ、アイコン、..といったものもカスタマイズ可能 OK APIレベル 8 から 19 までをサポート

デモ

  • ライブラリを使用したサンプルアプリケーションは、androidformenhancer-samplesフォルダに含まれています。

  • Google Playからダウンロードしてお試しいただけます。

インストール

Gradle / Android Studio

repositories {
    mavenCentral()
}
dependencies {
    compile 'com.github.ksoichiro:androidformenhancer:1.1.0@aar'
}

Eclipse

androidformenhancerフォルダがライブラリ本体です。 EclipseでAndroid Library Projectとして取り込んでください。

使い方

  1. 入力フォーム用のPOJOクラスを作成し、アノテーションでフォームの仕様を定義します。

    public class DefaultForm {
        @Widget(id = R.id.textfield_name)
        @Required
        public String name;
    
        @Widget(id = R.id.textfield_age, validateAfter = R.id.textfield_name)
        @IntType
        public String age;
    }
    
  2. 入力値を文字列以外のエンティティとして使用したい場合は、同名のフィールドを持つエンティティクラスを用意します。

    public class DefaultEntity {
        public String name;
        public int age;
    }
    
  3. ActivityやFragmentに下記のようなコードを書いて、画面から入力値を取り出すところから入力チェック、型変換を行ないます。

    ValidationResult result = new FormHelper(DefaultForm.class, this).validate();
    if (result.hasError()) {
        // エラーメッセージを表示します
        Toast.makeText(this, result.getAllSerializedErrors(), Toast.LENGTH_SHORT).show();
    } else {
        // entityは入力チェック・型変換の済んだオブジェクトです
        DefaultEntity entity = helper.create(DefaultEntity.class);
    }
    
  4. もしフォーカスが外れたタイミングで入力チェックしたい場合は、次のように書くだけです。

    new FormHelper(DefaultForm.class, this).setOnFocusOutValidation();
    

    ただし、これはテキストのフィールドだけに有効な方法です。

  5. android.app.Activity以外のクラスを使う場合は、ActivityFormHelperを別のクラスに置き換えてください。

    • android.support.v4.app.FragmentActivityを使う場合は、FragmentActivityFormHelperに置き換えます。
    • android.support.v4.app.Fragmentを使う場合は、SupportFragmentFormHelperに置き換えます。

入力値の取得

レイアウトから入力値を取得するには、まずFormクラスを作成します。 Formクラスはpublicなフィールドを持つだけの単なるPOJOクラスです。 全てのフィールドはpublicで、Stringjava.util.List<String>型である必要があります。

public class DefaultForm {
    public String name;
}

各フィールドは、android.widget.EditTextのようなウィジェットと関連付ける必要がります。 ウィジェットとの関連付けをするには、特別なアノテーションをフィールドに付与します。

EditText

<EditText>タグを使う場合は、@WidgetをFormクラスのフィールドに付与します。 例えば、res/layout/some_layout.xmlの一部が以下の通りだとします。

<EditText
    android:id="@+id/textfield_name"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

この場合、Formクラスの定義は次のようにします。

public class DefaultForm {
    @Widget(id = R.id.textfield_name)
    public String name;
}

RadioGroupとRadioButton

<RadioGroup>タグや<RadioButton>を使い、いずれかのラジオボタンが選択されていることを 検証したい場合は、@Widget@WidgetValueのアノテーションをFormクラスのフィールドに付与します。 例えば、res/layout/some_layout.xmlの一部が以下の通りだとします。

<RadioGroup
    android:id="@+id/rg_gender"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <RadioButton
        android:id="@+id/rb_male"
        android:text="男性"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <RadioButton
        android:id="@+id/rb_female"
        android:text="女性"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</RadioGroup>

この場合、Formクラスの定義は次のようにします。

public class DefaultForm {
    @Widget(id = R.id.rg_gender,
        values = {
            @WidgetValue(id = R.id.rb_male, value = "M"),
            @WidgetValue(id = R.id.rb_female, value = "F"),
        })
    public String gender;
}

もし"男性"のラジオボタンを選択すれば、DefaultForm#genderの値は"M"になります。

CheckBox

<CheckBox>タグを使用し、少なくとも1つ以上のチェックボックスがチェックされている ことを検証したい場合は、@Widget@WidgetValueアノテーションを Formクラスのフィールドに付与します。 例えば、res/layout/some_layout.xmlの一部が以下の通りだとします。

<LinearLayout
    android:id="@+id/cbg_sns"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">
    <CheckBox
        android:id="@+id/cb_facebook"
        android:text="Facebook"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <CheckBox
        android:id="@+id/cb_googleplus"
        android:text="Google+"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <CheckBox
        android:id="@+id/cb_twitter"
        android:text="Twitter"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

この場合、Formクラスの定義は次のようにします。

public class DefaultForm {
    @Required(atLeast = 1)
    @Widget(id = R.id.cbg_sns,
        values = {
            @WidgetValue(id = R.id.cb_facebook, value = "FB"),
            @WidgetValue(id = R.id.cb_googleplus, value = "GP"),
            @WidgetValue(id = R.id.cb_twitter, value = "TW")
        })
    public List<String> sns;
}

もし"Facebook"と"Google+"のチェックボックスを選択したならば、 DefaultForm#snsの値は"FB"と"GP"の2つの要素を持つList<String>になります。 @Widgetは、単に同じ種類のCheckBoxをグループ化するためだけに使用しています。 この例にあるLinearLayoutだけでなく、他のViewGroupのサブクラスである RelativeLayoutなども@Widgetと関連付けられることに注意してください。

Spinner

<Spinner>タグを使う場合は、@WidgetアノテーションをFormクラスのフィールドに付与します。 例えば、res/layout/some_layout.xmlの一部が以下の通りだとします。

<Spinner
    android:id="@+id/spn_credit_card_type"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

この場合、Formクラスの定義は次のようにします。

public class DefaultForm {
    @Widget(id = R.id.spn_credit_card_type, Widget.Type.SPINNER)
    public String creditCardType;
}

もし、先頭の要素を"選択してください"のようなダミー文字列として使いたい場合は、 @Requiredアノテーションをフィールドに追加し、Required#otherThanHeadtrueに設定します。 これにより、先頭の要素以外が選択されているかどうかを検証することができます。

検証クラス

以下の検証クラスが利用できます。

  1. RequiredValidator
    • EditTextの値がnullや空文字列でないことを検証します。
    • 対象フィールドは、Formクラスに定義され@Requiredアノテーションを付与されている必要があります。
    • 条件付きでチェックした場合は、次のように@Whenを併せて使います。
      public class CustomRequiredWhenForm {
          @Widget(id = R.id.spn_reason, nameResId = R.string.form_custom_required_when_reason)
          public String reason;
      
          @Widget(id = R.id.textfield_reason_other, nameResId = R.string.form_custom_required_when_reason_other,
                  validateAfter = R.id.spn_reason)
          @Required(when = {
              @When(id = R.id.spn_reason, equalsTo = "2")
          })
          public String reasonOther;
      }
      
      もしユーザがR.id.spn_reasonのSpinnerから3番目の選択肢を選び、
      ユーザがR.id.textfield_reason_otherのテキストフィールドに入力しなかった場合、
      この検証クラスはエラーとみなします。
  2. IntTypeValidator
    • EditTextの値が整数値(Integer)の形式であることを検証します。
    • 対象フィールドは、Formクラスに定義され@IntTypeアノテーションを付与されている必要があります。
  3. FloatTypeValidator
    • EditTextの値が浮動小数点数(Float)の形式であることを検証します。
    • 対象フィールドは、Formクラスに定義され@FloatTypeアノテーションを付与されている必要があります。
  4. MaxValueValidator
    • EditTextの値が指定の値以下であることを検証します。
    • 対象フィールドは、Formクラスに定義され@MaxValueアノテーションを付与されている必要があります。
  5. MinValueValidator
    • EditTextの値が指定の値以上であることを検証します。
    • 対象フィールドは、Formクラスに定義され@MinValueアノテーションを付与されている必要があります。
  6. IntRangeValidator
    • EditTextの値が指定の範囲にあることを検証します。
      最小値はIntRange#min()、最大値はIntRange#max()で指定します。
    • 対象フィールドは、Formクラスに定義され@IntRangeアノテーションを付与されている必要があります。
  7. DigitsValidator
    • EditTextの値が半角数字のみで構成されていることを検証します。
    • 対象フィールドは、Formクラスに定義され@Digitsアノテーションを付与されている必要があります。
  8. AlphabetValidator
    • EditTextの値が半角英字のみで構成されていることを検証します。
    • 対象フィールドは、Formクラスに定義され@Alphabetアノテーションを付与されている必要があります。
    • もし半角スペースも許可したい場合は、Alphabet#allowSpace()trueに設定します。
  9. AlphaNumValidator
    • EditTextの値が半角英字のみで構成されていることを検証します。
    • 対象フィールドは、Formクラスに定義され@AlphaNumアノテーションを付与されている必要があります。
    • もし半角スペースも許可したい場合は、AlphaNum#allowSpace()trueに設定します。
  10. HiraganaValidator
    • EditTextの値が全角ひらがなのみで構成されていることを検証します。
    • 対象フィールドは、Formクラスに定義され@Hiraganaアノテーションを付与されている必要があります。
  11. KatakanaValidator
    • EditTextの値が全角カタカナのみで構成されていることを検証します。
    • 対象フィールドは、Formクラスに定義され@Katakanaアノテーションを付与されている必要があります。
  12. SinglebyteValidator
    • EditTextの値がシングルバイト文字のみで構成されていることを検証します。
    • 対象フィールドは、Formクラスに定義され@Singlebyteアノテーションを付与されている必要があります。
  13. MultibyteValidator
    • EditTextの値がマルチバイト文字列であることを検証します。
    • 対象フィールドは、Formクラスに定義され@Multibyteアノテーションを付与されている必要があります。
  14. LengthValidator
    • EditTextの値が指定の文字数であることを検証します。
    • 対象フィールドは、Formクラスに定義され@Lengthアノテーションを付与されている必要があります。
  15. MaxLengthValidator
    • EditTextの値が指定の値以下であることを検証します。
    • 対象フィールドは、Formクラスに定義され@MaxLengthアノテーションを付与されている必要があります。
  16. NumOfDigitsValidator
    • EditTextの値が指定の桁数であることを検証します。
    • 対象フィールドは、Formクラスに定義され@NumOfDigitsアノテーションを付与されている必要があります。
    • この検証クラスはLengthValidatorと似ていますが、この検証クラスでは
      NumOfDigits#value()で指定する桁数(文字数)と一致していなくても、
      半角数字以外の文字が含まれていた場合にはエラーとして扱いません。
  17. MaxNumOfDigitsValidator
    • EditTextの値が指定の桁数以下であることを検証します。
    • 対象フィールドは、Formクラスに定義され@MaxNumOfDigitsアノテーションを付与されている必要があります。
    • この検証クラスはMaxLengthValidatorと似ていますが、この検証クラスでは
      MaxNumOfDigits#value()で指定する桁数(文字数)を超えていても、
      半角数字以外の文字が含まれていた場合にはエラーとして扱いません。
  18. DatePatternValidator
    • EditTextの値が日付形式であることを検証します。
    • 対象フィールドは、Formクラスに定義され@DatePatternアノテーションを付与されている必要があります。
    • 日付形式にはjava.text.DateFormat.SHORTが使用されます。これはロケールにより変化します。
    • 検証に使用する日付形式をカスタマイズする場合は、DatePattern#value()を使用してください。
  19. PastDateValidator
    • EditTextの値が正しい日付形式であり、過去の日付であることを検証します。
    • 対象フィールドは、Formクラスに定義され@PastDateアノテーションを付与されている必要があります。
    • 日付形式にはjava.text.DateFormat.SHORTが使用されます。これはロケールにより変化します。
    • 検証に使用する日付形式をカスタマイズする場合は、PastDate#value()を使用してください。
    • 今日の日付をエラーとみなしたくない場合は、PastDate#allowTodaytrueに設定してください。
  20. EmailValidator
    • EditTextの値がEメールアドレスの形式であることを検証します。
    • 対象フィールドは、Formクラスに定義され@Emailアノテーションを付与されている必要があります。
    • 検証に使用されるEメールアドレスの形式(正規表現)は次の通りです: ^[\\w-]+(\\.[\\w-]+)*@([\\w][\\w-]*\\.)+[\\w][\\w-]*$
    • 検証に使用するEメールアドレスの形式をカスタマイズする場合は、
      afeValidatorDefinitionsafeCustomEmailPatternを使用してstyleに形式を定義してください。
  21. RegexValidator
    • EditTextの値が指定の正規表現にマッチすることを検証します。
    • 対象フィールドは、Formクラスに@Regexアノテーションを付与されている必要があります。
    • 検証に使用する正規表現はRegex#value()で指定します。

検証の順序

各項目の検証順序は、Widget#validateAfterを使用して定義します。 例えば、以下のように定義した場合は、nameageの順番に検証されます。 画面の表示順とは異なることに注意してください。

public class DefaultForm {
    @Widget(id = R.id.textfield_name)
    @Required
    public String name;

    @Widget(id = R.id.textfield_age, validateAfter = R.id.textfield_name)
    @IntType
    public String age;
}

カスタマイズ

ライブラリの挙動やメッセージは、以下のようにカスタマイズすることができます。

  1. 停止ポリシー

    検証クラスがエラーを検出したときに、そのまま続行するか停止するかを制御します。 例えば、エラーを検出しても全項目を検証し、全てのエラーを表示したい場合は 以下のようにテーマを定義します。

    <style name="YourTheme">
        <item name="afeValidatorDefinitions">@style/YourValidatorDefinitions</item>
    </style>
    
    <style name="YourValidatorDefinitions" parent="@style/AfeDefaultValidators">
        <item name="afeStopPolicy">continueAll</item>
    </style>
    
  2. 利用可能な検証クラス

    標準で利用可能な検証クラスは、有効/無効を切り替えることができ、 独自の検証クラスを追加することもできます。 例えば、RequiredValidatorだけを有効にしたい場合は、以下のようにテーマを定義します。

    <string-array name="your_standard_validators">
        <item>com.androidformenhancer.validator.RequiredValidator</item>
    </string>
    
    <style name="YourTheme">
        <item name="afeValidatorDefinitions">@style/YourValidatorDefinitions</item>
    </style>
    
    <style name="YourValidatorDefinitions" parent="@style/AfeDefaultValidators">
        <item name="afeStandardValidators">@array/your_standard_validators</item>
    </style>
    
  3. 検証エラーメッセージ

    検証エラーメッセージは上書きすることができます。 例えば、RequiredValidatorのエラーメッセージを上書きしたい場合は 以下のようにテーマを定義します。

    <string name="custom_msg_validation_required">%1$sは絶対に入力してください!</string>
    
    <style name="YourTheme">
        <item name="afeValidatorMessages">@style/YourValidatorMessages</item>
    </style>
    
    <style name="YourValidatorMessages">
        <item name="afeErrorRequired">@string/custom_msg_validation_required</item>
    </style>
    

    エラーメッセージに含める項目名は、デフォルトではFormクラスに定義するフィールド名が使用されます。 この項目名を変更したい場合は、アノテーションのnameResId属性を使用してください。 例えば、以下のようにフィールドを定義します。

    @Widget(id = R.id.textfield_name)
    @Required
    public String firstName;
    

    この場合、エラーメッセージは「firstNameは必ず入力してください」となります。 項目名をカスタマイズする場合、Formは以下のように定義します。

    @Widget(id = R.id.textfield_name, nameResId = R.string.first_name)
    @Required
    public String firstName;
    

    もしくは下記の形式です。

    @Widget(id = R.id.textfield_name)
    @Required(nameResId = R.string.first_name)
    public String firstName;
    

    strings.xmlを次のように記述した場合

    <string name="first_name">お名前(名)</string>
    

    エラーメッセージは「お名前(名)は必ず入力してください」となります。

  4. エラーアイコン

    入力チェックエラー時に表示されるアイコンは次のように変更することができます。

    <style name="YourTheme">
        <item name="afeValidatorDefinitions">@style/YourValidatorDefinitions</item>
    </style>
    
    <style name="YourValidatorDefinitions" parent="@style/AfeDefaultValidators">
        <item name="afeValidationIconError">@drawable/your_icon_error</item>
        <item name="afeValidationIconOk">@drawable/your_icon_ok</item>
    </style>
    

ビルド

Gradleラッパーを使ったビルド

このプロジェクトにはGradleラッパーが含まれており、このプロジェクトの開発で使用されています。 安定したビルドのために、ライブラリを利用するプロジェクトでもGradleラッパーを同じ設定で利用していただくことを推奨します。

ProGuard

ProGuardを使用する場合は、以下のようにproguard-project.txtを編集してください。

  1. Validatorのクラス名を維持します。これは常に必須です。

     -keep class com.androidformenhancer.validator.* { <init>(...); }
    
  2. FormクラスとEntityクラスのメンバー(publicなフィールド)名を維持します。 FormHelper#create()@Whenを使用する場合は必須です。

     -keepclassmembers class com.androidformenhancer.sample.demos.DefaultForm {
       public *;
     }
     -keepclassmembers class com.androidformenhancer.sample.demos.DefaultEntity {
       public *;
     }
    

テスト

テストの実行

ユニットテスト

JUnitテストコードはtestsフォルダに含まれています。 以下のコマンドでテストを実行できます。

./gradlew :androidformenhancer:connectedAndroidTest

Dockerを利用したビルド

手元にAndroid開発環境がなくても、Dockerがインストールされていれば、 以下のコマンドでDockerを使用してテストを実行することができます。

./test-docker.sh

注意: 現在、このスクリプトは動作しません。

Continuous Integration

このプロジェクトはTravis CIでmasterブランチへのPushをトリガーとしてビルドされています。

テスト結果のレポート

ユニットテストレポート

androidformenhancer/build/reports/androidTests/connected/index.html

JaCoCoによるカバレッジ計測レポート

ライブラリ本体部分(androidformenhancerフォルダ)のカバレッジをJaCoCoを使って計測しています。 Travis CIでのビルド結果はCoverallsで確認できます。

androidformenhancer/build/reports/coverage/debug/index.html

開発者

ライセンス

Copyright 2012 Soichiro Kashima

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Package Rankings
Top 23.92% on Repo1.maven.org
Badges
Extracted from project README
Build Status Coverage Status Maven Central Demo on Google Play
Related Projects