【Android】リアルタイムバリデーション機能付きのパスワード入力欄を作る

## はじめに
こんな感じでバリデーションエラーの時は赤く、それ以外の時は白くなるパスワード入力欄ありますよね。
Androidでこれを実装したかったのですが、レイアウトを出し分けるやり方がなかなか見つからず苦労しました。
なので今回はその備忘録を記したいと思います。

![validation_test.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1274260/10efbbba-aff3-361c-dd4a-238a781d4dce.gif)

## 実行環境
|  項目      |  情報                |      
| ------ | ---------------------------- | 
| PC     | MacBook Pro (14-inch,2021) |     
| CPU    | Apple M1 Pro 10-core                |      
| GPU  | Apple M1 Pro 16-core                       |      
| OS     | macOS Monterey (12.0.1)<br>  |   
|  Android Studio| Arctic Fox 2020.3.1 Patch4 | 

## 作成手順
1. 背景用のdrawableを2つ作成（エラー用、標準時用）
1. ViewModelでバリデーションエラーメソッドの実装
1. レイアウトファイル内のEditTextに1と2を適用
1. ActivityでレイアウトとViewModelの結合

今回は8文字以下ならEditTextを赤くしてエラーを示すという形で作っていきます。

## 1. 背景用のdrawableを2つ作成（エラー用、標準時用）
EditTextの背景に適用させる角丸のdrawableを2つ（エラー用、標準時用）作っておきます。
shapeやstrokeなどはお好みでどうぞ。

```edittext_error.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape android:shape="rectangle">
            <corners android:radius="10dp" />
            <solid android:color="#FFE4E1" />
            <stroke android:width="1dp" android:color="#FF0000" />
        </shape>
    </item>
</selector>
```

```edittext_normal.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape android:shape="rectangle">
            <corners android:radius="10dp" />
            <solid android:color="#f5f5f5" />
            <stroke android:width="1dp" android:color="#808080" />
        </shape>
    </item>
</selector>
```

## 2. ViewModelでバリデーションエラーメソッドの実装
「入力された文字列が8文字以下ならエラー」というメソッドを具体的に記述していきます。
ここを変えればもっと複雑なバリデーションもかけられるようになります。

``` MainViewModel.kt
class MainViewModel() : ViewModel() {

    // エラーかどうか判定する変数を置いておく
    val isError: MutableLiveData<Boolean> = MutableLiveData()

    // 入力された文字列が8文字以下ならisErrorをtrueにする
    fun checkError(editable: Editable){
        if (editable.length < 8){
            isError.value = true
        }else{
            isError.value = false
        }
    }
}
```

## 3. レイアウトファイル内のEditTextに1と2を適用

とりあえずシンプルにEditTextを1つだけ配置します。

```activity_main.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="viewModel"
            type="com.example.firstapplication.MainViewModel" />
    </data>

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="30dp"
        android:afterTextChanged="@{viewModel::checkError}"
        android:background="@{viewModel.isError ?@drawable/edittext_error : @drawable/edittext_normal}"
        android:padding="10dp" />
</layout>
```

まず、```android:afterTextChanged```に関数を設定し、入力した文字列をMainViewModel内の```editable```で受け取っています。

そうしてバリデーションが通ったか否かを判定し、```android:background```で背景を適用させます。
ここでは三項演算子を使用し、```isError```がtrueなら```@drawable/edittext_error```を、falseなら```@drawable/edittext_normal```を適用させるようにしています。

## 4. ActivityでレイアウトとViewModelの結合


```MainActivity.kt
class MainActivity : AppCompatActivity() {

    var binding: ActivityMainBinding? = null
    val viewModel = MainViewModel()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.activity_main)
        binding = DataBindingUtil.setContentView(this,R.layout.activity_main)
        binding?.apply {
            lifecycleOwner = this@MainActivity
            viewModel = this@MainActivity.viewModel
        }
    }
}
```

MainActivityではレイアウトを作成し、その上でレイアウトファイルのlifecycleOwnerとviewModelをそれぞれ設定しています。
この辺りの書き方は色々流派があると思いますが、いずれにせよ```binding?.apply```内の記述がないとうまく動かず私みたいにハマる羽目になるので、最後まで気を抜かずに確認しましょう。[参考](https://qiita.com/shanonim/items/2c8f5cfc20c16b8bb8b6)

ここまで書けば一通り完成です！

## ハマりポイント

### カスタムビュー上だと見た目が変化しない
今回はxmlで```<EditText>```を使っていますが、ここを独自に作成したカスタムビューにするというケースは往々にしてあると思います。
ですが私が試した限りですと、ここをカスタムビューにしてしまうとうまく動きませんでした。

### 三項演算子でdrawableを設定しようとすると補完が効かない
xmlのEditTextタグ内で```        android:background="@{viewModel.isError ?@drawable/edittext_error : @drawable/edittext_normal}"```とやっている部分も注意が必要です。
結構長い記述なのですが、ここだけなぜか補完が効きません。
そのため、書いた後は一旦drawableファイル名の上でcmd+B（windowsの場合はctrl+B）を押し、該当のファイルに飛べるかどうか確認してください。
これで飛べれば正常に書けている証拠です。