はじめに
メソッドの返り値をCall<Void>
にすればリクエスト取り出せますね。
CallAdapterFactoryを追加したら普通のCall<T>
で返せないかと思ってました。
まちがい
例えばこんなinterfaceがあります。
public interface SuzuriAPI {
@GET("/api/v1/activities")
Single<Activities> activities();
@GET("/api/v1/activities/unreads")
Single<Unreads> activitiesUnreads();
}
これはGMOペパボが運営しているSUZURIというサービスのAPIを利用するために書いたものです。
「自分だけのオリジナルグッズが作れる。売れる。買える。」というやつです。
https://suzuri.jp/
https://suzuri.jp/developer
https://twitter.com/suzurijp
こんな風にして使います。
OkHttpClient client;
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://suzuri.jp/")
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
api = retrofit.create(SuzuriAPI.class);
api.activities().subscribe(new Consumer<Activities>() {
@Override
public void accept(Activities activities) throws Exception {
// レスポンスのjsonがGsonでPOJOに変換されたものをRxのSingle経由で受け取ります。
// なお、このActivitiesはAndroidのActivityとは無関係です。
}
});
実際には、APIを利用する前に、ユーザに認可してもらって、アクセストークンを取得する必要があります。
一部だけ自分でURLを組み立てるのも微妙ですし、Retrofit用のinterfaceは視認性が良いので、
このOAuth部分(認可とアクセストークン取得)についてもAPIの定義と同じように書いておきたいですね。
(APIとは別ファイルに書くべきかもしれませんが、ここではまとめます。)
アクセストークンの取得はAPI部分と同じように書いて、Retrofit経由で通信することができます。
public interface SuzuriAPI {
// アクセストークンの取得もRetrofitで定義
@FormUrlEncoded
@POST("/oauth/token")
Single<AccessToken> accessToken(@Field("grant_type") String grant_type,
@Field("code") String code,
@Field("redirect_uri") String redirect_uri,
@Field("client_id") String client_id,
@Field("client_secret") String client_secret);
}
ところが、OAuthでの認可画面はAndroidだとChromeなどの外部ブラウザで表示します。
つまり、Retrofit経由では通信しません。
そして、Retrofitでは実際のリクエストが隠蔽されています。
そのため、interfaceに定義しておいても組み立てられたURLを取り出すことができません。
public interface SuzuriAPI {
// 認可画面はブラウザで行うため、Retrofitでは通信しない
@GET("/oauth/authorize")
Single<?> authorize(@Query("client_id") String client_id,
@Query("scope") String scope,
@Query("redirect_uri") String redirect_uri,
@Query("response_type") String response_type);
}
そこで、Retrofitが実際の通信に使っているOkHttpClientに、Interceptorを追加します。
インターセプトの条件については、ちょっと邪悪ですが、ヘッダーに目印を付けます。
@Headers("X-Dummy: Dummy")
@GET("/oauth/authorize")
Single<Dummy> authorize(
Interceptorでそのヘッダーを見つけたら、ダミーのレスポンスを作って返します。
OkHttpClient client = defaultClient.newBuilder()
.addInterceptor(new Interceptor() {
@Override
public Response intercept(@NonNull Chain chain) throws IOException {
final Request original = chain.request();
if (original.header("X-Dummy") != null) {
Dummy dummy = new Dummy(original.url().toString());
ResponseBody body = ResponseBody.create(null, gson.toJson(dummy));
return new Response.Builder()
.request(original)
.protocol(Protocol.HTTP_1_0)
.code(200)
.message("OK")
.body(body)
.build();
}
return chain.proceed(original);
}
})
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://suzuri.jp/")
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
public class Dummy {
public final String value;
public Dummy(String value) {
this.value = value;
}
}
Responseのrequest
とprotocol
とcode
とmessage
は必須なので、適当な値を設定しています。
以下のようにして取り出します。
api.authorize(CLIENT_ID, SCOPE, REDIRECT_URI, "code")
.subscribe(new Consumer<Dummy>() {
@Override
public void accept(Dummy dummy) throws Exception {
// dummy.valueが組み立てられたURL
}
});
(Retrofitにまともに触る初日なので単に見落としてるだけかもしれません……)
おわりに
冒頭で述べたようにCall<Void>
を返すメソッドを定義しましょう