Zxcvbn + RxBinding で入力されたパスワードの強度をチェックする


EditTextに入力されたパスワードを検知して強度をチェックするという内容が、こちらの記事で紹介されていたので試してみました。
RxBindingがRxJava2に対応していたので、本記事ではそちらを使用しています。

ライブラリを準備

以下のライブラリを使用します。

app/build.gradle
    compile "io.reactivex.rxjava2:rxjava:2.1.3"
    compile 'com.jakewharton.rxbinding2:rxbinding:2.0.0'
    compile 'com.nulab-inc:zxcvbn:1.2.3'

※上記に記載していませんが、retrolambdaを使ってラムダ式で記述します。

zxcvbn4jについて

DropBoxが公開しているパスワード強度チェックライブラリzxcvbnのJava移植版で、Nulab社から公開されています。

READMEに記載の通り、使い方も非常に簡単で、measure メソッドに文字列を渡すと結果を得られます。

Zxcvbn zxcvbn = new Zxcvbn();
Strength strength = zxcvbn.measure("This is password");

レイアウトを作成

パスワードを入力するEditTextを追加します。

<EditText
    android:id="@+id/password"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:inputType="textPassword"
    android:maxLines="1" />

入力されたパスワードに応じて、結果を表示するViewを追加します。
zxcvbnのスコアは、0〜4の整数で返ってくるので、android:max="4"としています。
弱いパスワード [0] <-----> [4] 強いパスワード

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <ProgressBar
        android:id="@+id/password_strength_bar"
        style="?android:progressBarStyleHorizontal"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:max="4" />

    <TextView
        android:id="@+id/password_strength_text"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="4"
        android:gravity="center"
        android:minLines="2" />

</LinearLayout>

入力を検知して、結果を更新する。

RxBindingを使ってEditTextの内容変更を通知、zxcvbnでスコアを測定して、結果を他のViewに渡します。

SignUpActivity.java
Zxcvbn zxcvbn = new Zxcvbn();
// EditTextをObservable化
RxTextView.textChanges(mPasswordEditText)
     // スコアの測定は計算スレッドで行う
        .observeOn(Schedulers.computation())
        .map(CharSequence::toString)
        .map(zxcvbn::measure)
     // 0〜4の整数でスコアを取得
        .map(Strength::getScore)
         // メインスレッドで他のViewに渡す
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(score -> {
            mStrengthProgressBar.setProgress(score);
            mStrengthTextView.setText(PASSWORD_STRENGTH_MESSAGES[score]);
        });

ここでは、以下のような配列を用意して、スコアに応じたテキストをTextViewに渡しています。

private static final String[] PASSWORD_STRENGTH_MESSAGES = new String[] {
        "Weak", "Fair", "Good", "Strong", "Very Strong"
};

元の記事では、 MissingBackpressureExceptionの回避のために.onBackpressureLatest() を指定していましたが、RxJava2ではBackPressureがFlowableのみの機能となりました。
Flowableはサーバサイドなどで大量のデータを扱う場合を想定しているようで、GUIイベント程度の少量のデータであれば、オーバーヘッドの少ないObservableを使う方が良いとのことなので、削っています。
https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#observable-and-flowable
入力の度に無駄な処理を走らせたくないのであれば、Throttlingで入力後に間隔が空いたら通知するようにすれば良さそう。

その他

strength.getScore() ではなく、 strength.getFeedback()とすると、そのパスワードに対してのフィードバックが得られます。
ローカライズにも対応していて getFeedback().getSuggestions(Locale.JAPAN)のようにすると上記のGIFのように日本語で修正の提案のメッセージが得られます。便利!