*追記1:enum の場合について最後に追記しました。
*追記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);
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);
- }
- }
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;
- }
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;
- }
- }
- }
@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;
- }
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;
- }
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;
- }
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;
}