*追記2:まずは int 型じゃねーよ、enumだろって vvakameさんに怒られたけど、もともとは、とあるプロジェクトでサーバーに意図しない値がきてるんだけど、、、みたいなことがあって、サーバーに渡す値を静的に制限するにはどうするのがいいのかな、というのが出発点だったのです。なんで最初が int かっつーと、そのときのコードが int だったからだよっ(つまり初心者がやりがちってこと)
例えば T シャツのサイズをユーザーに選択してもらう画面があったとします。
Tシャツのサイズは L, M, S で、サーバーに投げるときはそれぞれ int値 の 1, 2, 3 として渡します。
まずは int 型で、ってなりますよね。
private int size;
@Override
public void onSizeSelected(int size) {
this.size = size;
}
@OnClick(R.id.send_button)
void onSendButtonClicked() {
send(size);
}
@POST("/tshirt-size")
boolean send(@Query("size") int size);
これだと、ユーザーが選択していない状態で送信ボタンを押すと 0 が送られてしまうので、メッセージを表示して送信をブロックしましょう。そのためには未選択状態の値を定義しないといけません。よくあるのは -1 で、こんな感じになるでしょう。
private int size = -1;
@Override
public void onSizeSelected(int size) {
this.size = size;
}
@OnClick(R.id.send_button)
void onSendButtonClicked() {
if (size == -1) {
Toast.makeText(context, "サイズを選択してください", Toast.LENGTH_SHORT).show();
} else {
send(size);
}
}
悪くないんですが、-1 を弾くよりサーバーに送るデータ を 1, 2, 3 に制限したほうがよさそうです。そこで @IntDef を使って次のようにしてみます。
public static final int SIZE_L = 1;
public static final int SIZE_M = 2;
public static final int SIZE_S = 3;
@Retention(RetentionPolicy.SOURCE)
@IntDef({SIZE_L, SIZE_M, SIZE_S})
public @interface ValidSize {
}
private int size = -1;
@Override
public void onSizeSelected(@ValidSize int size) {
this.size = size;
}
@OnClick(R.id.send_button)
void onSendButtonClicked() {
if (size == -1) {
Toast.makeText(this, "サイズを選択してください", Toast.LENGTH_SHORT).show();
} else {
send(size); // ここでエラーになる
}
}
@POST("/tshirt-size")
boolean send(@ValidSize int size) {
return true;
}
これだと send(size); のところでエラーがでます。size が @ValidSize ではないからですね。次のように値をチェックすればエラーは出なくなりますが、int から int 変換ですしどうもいまいちです。
@OnClick(R.id.send_button)
void onSendButtonClicked() {
if (size == -1) {
Toast.makeText(this, "サイズを選択してください", Toast.LENGTH_SHORT).show();
} else {
switch (size) {
case SIZE_L:
send(SIZE_L);
break;
case SIZE_M:
send(SIZE_M);
break;
case SIZE_S:
send(SIZE_S);
break;
}
}
}
そこで @ValidSize の int 値を保持するクラスを用意してみます。未設定かどうかを保持する boolean 値も持たせます。
private static class Size {
public static final int SIZE_L = 1;
public static final int SIZE_M = 2;
public static final int SIZE_S = 3;
@Retention(RetentionPolicy.SOURCE)
@IntDef({SIZE_L, SIZE_M, SIZE_S})
public @interface ValidSize {
}
@ValidSize
private int size;
private boolean isValid = false;
public void setSize(@ValidSize int size) {
this.size = size;
this.isValid = true;
}
@ValidSize
public int getSize() {
return size;
}
public boolean isValid() {
return isValid;
}
}
private Size size = new Size();
@Override
public void onSizeSelected(@Size.ValidSize int size) {
this.size.setSize(size);
}
@OnClick(R.id.send_button)
void onSendButtonClicked() {
// ここのチェックを強制できない
if (!size.isValid()) {
Toast.makeText(this, "サイズを選択してください", Toast.LENGTH_SHORT).show();
} else {
send(size.getSize());
}
}
@POST("/tshirt-size")
boolean send(@Size.ValidSize int size) {
return true;
}
これで不細工なswitch文にさよならできましたが、問題があります。size.isValid() でチェックすることを利用側に強制できません。
そこで、null を未設定状態として扱うようにしてみます。
private static class Size {
public static final int SIZE_L = 1;
public static final int SIZE_M = 2;
public static final int SIZE_S = 3;
@Retention(RetentionPolicy.SOURCE)
@IntDef({SIZE_L, SIZE_M, SIZE_S})
public @interface ValidSize {
}
public static Size valueOf(@ValidSize int size) {
return new Size(size);
}
@ValidSize
private final int size;
private Size(@ValidSize int size) {
this.size = size;
}
@ValidSize
public int getValue() {
return size;
}
}
@Nullable
private Size size = null;
@Override
public void onSizeSelected(@Size.ValidSize int size) {
this.size = Size.valueOf(size);
}
@Override
public void onSizeCleared() {
this.size = null;
}
@OnClick(R.id.send_button)
void onSendButtonClicked() {
if (size == null) {
Toast.makeText(this, "サイズを選択してください", Toast.LENGTH_SHORT).show();
} else {
send(size.getValue());
}
}
@POST("/tshirt-size")
boolean send(@Size.ValidSize int size) {
return true;
}
これで null じゃないときに取得できる値を @Size.ValidSize に制限できます。size に @Nullable をつければ、null チェックをしないで size.getValue() を呼び出そうとしたところで Lint の警告が出てくれます。
size に null を代入することでサイズ選択のクリアもできます。
未選択状態に特定の値を割り当てる場合、その値が絶対使われないならいいのですが、使われる場合もありえます。 例えば、透明度を含む色を選択してもらいたい場合では #ffffffff が -1 なので、-1を未選択状態に割り当てるのは不適当になります。
null を未選択状態に割り当てる方法はこういう場合にも適用できます。
追記 :
@vvakame から enum 使えよおらーって言われたので、@zaki50 さんの提案をもとに enum版も載せておきます。
前提として、send()に渡される引数の値を制限したいというのが目的です。 retrofit で引数のパラメータに enum を渡すと toString() の値が利用されるようで、なにかしらコンバーターを仕込まないといけなさそうです。 なので、send() に渡す値に @ValidSize をつけるのはそのままにしたいと思います。
こんな感じになります。ほぼ同じですね。
違いは、onSizeSelected() の引数が @ValidSize int size から Size になったので、この部分の値の制限が Lint からコンパイラになったという点と、 enum なので == で比較できるという点くらいでしょうか。
ちなみにこの書き方だと SIZE_L などを static import する必要があります。Ctrl + space2回 で候補がでます。
public enum Size {
L(SIZE_L), M(SIZE_M), S(SIZE_S);
@Retention(RetentionPolicy.SOURCE)
@IntDef({SIZE_L, SIZE_M, SIZE_S})
public @interface ValidSize {
int SIZE_L = 1;
int SIZE_M = 2;
int SIZE_S = 3;
}
@ValidSize
private final int size;
Size(@ValidSize int size) {
this.size = size;
}
@ValidSize
public int getValue() {
return size;
}
}
@Nullable
private Size size = null;
@Override
public void onSizeSelected(Size size) {
this.size = size;
}
@Override
public void onSizeCleared() {
this.size = null;
}
@OnClick(R.id.send_button)
void onSendButtonClicked() {
if (size == null) {
Toast.makeText(this, "サイズを選択してください", Toast.LENGTH_SHORT).show();
} else {
send(size.getValue());
}
}
@POST("/tshirt-size")
boolean send(@Size.ValidSize int size) {
return true;
}
0 件のコメント:
コメントを投稿