[Android] Retrofit2 x RxJavaで通信処理をする

2016.03.23

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

Retrofitが2.0にバージョンアップされましたが、互換性がないため2.0未満の時と同じ書き方だと動作しないようです。
そこでRetrofit1.9で以前書いた弊社ブログのコードを基に変更点を見ながらRetrofit2.0で動作するコードに変更していこうと思います。

既存コード

public interface IWhetherApi {

    @GET("/{fileName}")
    Observable<WetherResponse> getWhether(@Path(value = "fileName", encode = false) String fileName,
                    @Query("id") String id,
                    @Query("APPID") String appId);

}
        String id    = "2172797";
        String appId = "464b981be2248f383abxxxxxxxxxxx";
        String path  = "data/2.5/weather";
        
        RestAdapter restAdapter = new RestAdapter.Builder()
                .setEndpoint("http://api.openweathermap.org")
                .build();
                
        restAdapter.create(IWhetherApi.class).getWhether(path, id, appId)
                .subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<WetherResponse>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onNext(WetherResponse wetherResponse) {

                    }
                });

以前書いたブログのコードです。ここからRetrofitが2.0仕様に変更していきます。
上記コード5-7行目のrestAdapter周りの仕様が大きく変更しました。

Retrofit2仕様に変更する

[変更前]

        RestAdapter restAdapter = new RestAdapter.Builder()
                .setEndpoint("http://api.openweathermap.org")
                .build();

[変更後]

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://api.openweathermap.org")
                .build();

RestAdapterクラスがRetrofitクラスに変更になり、エンドポイントを指定するメソッドがsetEndpoint()からbaseUrl()に変更しました。

これで実行すると以下のexceptionが発生します。

java.lang.IllegalArgumentException: Could not locate call adapter for rx.Observable

これはRxJavaCallAdapterFactoryがないために発生します。

java.lang.IllegalArgumentException: Could not locate ResponseBody converter for class

これはGsonConverterFactoryがないために発生します。
Retrofit1.9以前はBuilder内でsetConverter(new GsonConverter(GSON))と書いて任意の命名規則や日付のフォーマットを指定したり、エンティティーオブジェクトを用意して変数名に気をつけていれば記述をしなくても良かったのですが
Retrofit2では記述をしなければ上記のexceptionが発生します。

以上2点を踏まえて書くと以下のようになります。

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://api.openweathermap.org")
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .build();

Retrofit1.9以前はBuilder内で以下のメソッドを実行していた場合、Retrofit2以降では書き方が変更になりました。
- setLogLevel()
- setRequestInterceptor()
- setClient()

        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        
        OkHttpClient okHttpclient = new OkHttpClient.Builder()
                .addInterceptor(loggingInterceptor)
                .build();
                
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://api.openweathermap.org")
                .client(okHttpclient)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .build();

ビルダーのメソッドのsetLogLevel()はなくなったのでRetrofit2以降はinterceptorでログを定義します。
okHttpclientinterceptorを追加してRetrofit.Builderのclient()でinterceptorを追加したclientを渡します。

        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        
        OkHttpClient okHttpclient = new OkHttpClient.Builder()
                .addInterceptor(new RequestHeaderInterceptor())
                .addInterceptor(loggingInterceptor)
                .build();
                
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://api.openweathermap.org")
                .client(okHttpclient)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .build();
public class RequestHeaderInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        final Request.Builder builder = chain.request().newBuilder();
        builder.addHeader("headerKey","value");
        return chain.proceed(builder.build());
    }
}

ヘッダーを追加する場合はInterceptorを継承したクラスでヘッダーを定義してOkHttpClientaddInterceptor()で追加します。

public interface IWhetherApi {

    @Headers("Cache-Control: max-age=640000")
    @GET("/{fileName}")
    Observable<WetherResponse> getWhether(@Path(value = "fileName", encode = false) String fileName,
                    @Query("id") String id,
                    @Query("APPID") String appId);

}

もしくはインターフェースに@Headers()を使いheaderを追加する方法もあります。

Retrofit2でstethoを使用する場合は

        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        
        OkHttpClient okHttpclient = new OkHttpClient.Builder()
                .addInterceptor(new RequestHeaderInterceptor())
                .addInterceptor(loggingInterceptor)
                .addNetworkInterceptor(new StethoInterceptor())
                .build();
                
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://api.openweathermap.org")
                .client(okHttpclient)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .build();
public class App extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        
        Stetho.initialize(
                Stetho.newInitializerBuilder(this)
                .enableDumpapp(Stetho.defaultDumperPluginsProvider(this))
                .enableWebKitInspector(Stetho.defaultInspectorModulesProvider(this))
                .build());
    }
}

AndroidManifest.xml

<application
        android:name=".App" //Appクラスのパスを記述

stethoについて詳しく知りたい方はこちらの弊社ブログをご覧ください。

最後に

結構変更点がありますね。
他にも変更点はあるようなので見つけたらブログに書こうと思います。
最後に変更後のコードを貼っておきます。

build.gradle

    compile 'com.android.support:appcompat-v7:23.2.0'
    compile 'io.reactivex:rxjava:1.0.14'
    compile 'io.reactivex:rxandroid:1.0.1'
    compile 'com.google.code.gson:gson:2.6.1'
    compile 'com.squareup.okhttp3:okhttp:3.0.1'
    compile 'com.squareup.okhttp3:logging-interceptor:3.0.0'
    compile 'com.facebook.stetho:stetho:1.3.1'
    compile 'com.facebook.stetho:stetho-okhttp3:1.3.1'
    compile 'com.facebook.stetho:stetho-urlconnection:1.3.1'
    compile 'com.squareup.retrofit2:adapter-rxjava:2.0.0'
    compile 'com.squareup.retrofit2:converter-gson:2.0.0'
    compile 'com.squareup.retrofit2:retrofit:2.0.0'
public interface IWhetherApi {

    @GET("/{fileName}")
    Observable<WetherResponse> getWhether(@Path(value = "fileName", encode = false) String fileName,
                    @Query("id") String id,
                    @Query("APPID") String appId);

}
        String id    = "2172797";
        String appId = "464b981be2248f383ab898810c9d293a";
        String path  = "data/2.5/weather";
        
        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

        OkHttpClient okHttpclient = new OkHttpClient.Builder()
                .addInterceptor(new RequestHeaderInterceptor())
                .addInterceptor(loggingInterceptor)
                .addNetworkInterceptor(new StethoInterceptor())
                .build();

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://api.openweathermap.org")
                .client(okHttpclient)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .build();

        retrofit.create(IWhetherApi.class).getWhether(path,id,appId)
                .subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<WetherResponse>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onNext(WetherResponse wetherResponse) {
                        Log.d("next", String.valueOf(wetherResponse.main.tempMin));
                    }
                });
public class RequestHeaderInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        final Request.Builder builder = chain.request().newBuilder();
        builder.addHeader("headerKey","value");
        return chain.proceed(builder.build());
    }
}