ボクココ

個人開発に関するテックブログ

Volley + Gson + Generics = God! (Android で OAuth な Rest Api のクライアント作成)

Android アプリでよくあるパターンとしては Restful な Web API を呼んで、リストや詳細を表示などが挙げられる。こんなとき、JSONで通信しているのであれば、リクエストのパラメータを作り、レスポンスを解析するというコードを書く必要がある。これが例外処理やらnullチェックやらで何かと面倒。そう感じることが多かった。 最近、gsonとの出会いがあった。なるほど、これを使えばJSONをパースせずともGetter, Setter を持つモデルクラスを作ればそこに詰め込んでくれる優れものだ。だがもっとシンプルにできないだろうか。。

Gson と Generics の相性

Generics は型に縛られず共通処理を書けるようになるテクニックだ。これを使えば以下のようなシンプルなREST クライアントが作成できる。

色々書いて気づいたが、下記コードを見るだけだととてもわかりにくい。この部分だけ切り出したりできないか検討します。。

public class RestApi {
    public static <T> void index(String url, final Class<T[]> clazz, final ApiCallbackBase.ApiCallback<T[]> callback) {
        ApiRequest.get(url, getListHandler(clazz, callback));
    }

    private static <T> ApiResponseHandler getListHandler(final Class<T[]> clazz, final ApiCallbackBase.ApiCallback<T[]> callback) {
        final Context context = ApplicationController.getInstance().getApplicationContext();
        return new ApiResponseHandler() {
            @Override
            public void onSuccess(JSONObject jsonObject) {
                try {
                    Gson gson = new GsonBuilder()
                            .setDateFormat(context.getString(R.string.date_parse_in)).create();
                    LogUtil.d(jsonObject.getJSONArray("item").toString());
                    T[] dtoList = gson.fromJson(jsonObject.getJSONArray("item").toString(), clazz);
                    if (callback != null) {
                        callback.onSuccess(dtoList);
                    }
                } catch (JSONException e) {
                    onFailure(new ApiException(e.getMessage(), ApiException.JSON_PARSE_ERROR));
                }
            }

            @Override
            public void onFailure(ApiException e) {
                LogUtil.e("Index Failed..");
                LogUtil.e("msg:" + e.getMessage() + ", status: " + e.getStatusCode());
                if (callback != null) {
                    callback.onFailure(e.getMessage(), e.getStatusCode());
                }
            }
        };
    }

}

ApiRequest で Volley のhttp 通信を行なう。

public class ApiRequest {

    public static void get(String url, final ApiResponseHandler handler) {
        try {
            request(Request.Method.GET, url, null, handler);
        } catch (JSONException e) {
            handler.onFailure(new ApiException(e.getMessage(), ApiException.JSON_PARSE_ERROR));
        }
    }

    private static void request(final int method, final String url, final JSONObject params, final ApiResponseHandler handler) throws JSONException {
        RequestDto reqDto = new RequestDto(method, url, params);
        reqDto.setAccessToken();
        JsonObjectRequest req = new JsonObjectRequest(reqDto.method, reqDto.url, reqDto.params,
                new Response.Listener<JSONObject>() {
                    @Override
                    public void onResponse(JSONObject response) {
                        handleSuccessResponse(response, handler);
                    }
                }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError e) {
                LogUtil.e("Error: " + e.getMessage());
                e.printStackTrace();

                int status = ApiException.UNKNOWN_ERROR;
                if (e.networkResponse != null) {
                    status = e.networkResponse.statusCode;
                }
                if (status == 401) {
                    if (params != null && params.has(TokenParamDto.GRANT_REFRESH_TOKEN)) {
                        SignupActivity.redirectToSignup();
                    } else {
                        tokenRefresh(method, url, params, handler);
                    }
                    return;
                }
                try {
                    String responseBody = new String(e.networkResponse.data, "utf-8");
                    JSONObject jsonObject = new JSONObject(responseBody);
                    LogUtil.e(jsonObject);
                } catch (Exception e2) { }

                handler.onFailure(new ApiException(e.getMessage(), status));
            }
        }
        );
        ApplicationController.getInstance().addToRequestQueue(req);
    }

抜粋ではあるが、OAuthのHttpクライアントとしてリフレッシュトークン、アクセストークンのやりとりを実装するには自前でやるのが一番早い。これらを必要としないシンプルなREST Apiであれば、Retrofit などのOSSを使うことも検討できるだろう。ただ、中身がVolleyじゃなくなるのでそこら辺、自分としては気持ち悪いところ。そこまでこだわりなければ全然使っていいと思う。