EditText を拡張し、独自のアトリビュートを宣言し、それを読み取って使うときに注意する点。
onTextChanged コールバックはコンストラクタから呼ばれる
EditText の親の TextView には、TextView#onTextChanged(CharSequence, int, int, int)
というコールバックがあり、これを継承することで、TextWatcher
を使わなくても、セットしている文字列の変更をモニタできます。
このコールバックは、通常TextView#setText()
のような文字列の変更を行うメソッドの中で呼ばれますが、もう一つ、TextView を初期化するコンストラクタでも呼び出されています(Android 4.4.2 のコードベースではL3817あたり)。
オーバライドすることを許されたメソッドをコンストラクタで呼び出す危険性は EffectiveJava が説明している通りですが、もし継承によって拡張した独自の EditText が、TextView#onTextChanged(CharSequence, int, int, int)
をオーバライドして、コンストラクタで初期化している(と思っている)はずのオブジェクトを使用していた場合、容易に NullPointerException なり何かしらの実行時例外が起こってしまいます。
// super のコンストラクタで onTextChanged が呼ばれてしまう
public class MyEditText extends EditText {
private Something mSomething;
public MyEditText(Context context) {
super(context);
init(context, null, 0);
}
public MyEditText(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs, 0);
}
public MyEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs, defStyle);
}
private void init(Context context, AttributeSet attrs, int defStyle) {
mSomething = new Something();
}
@Override
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
super.onTextChanged(text, start, lengthBefore, lengthAfter);
mSomething.doSomething(); // コンストラクタで呼ばれてしまうので、mSomething が null のまま
}
}