2016年3月16日水曜日

Migrate from Retrofit to Retrofit2 (Retrofit から Retrofit2 に移行する)

retrofit2 が正式リリースされました。

retrofit/CHANGELOG.md at master · square/retrofit

retrofit との後方互換性はありません。そのため maven の group id が com.squareup.retrofit2 になっています。 compile 'com.squareup.retrofit2:retrofit:2.0.0'
Converter

GSON などの Converter は別のモジュールに分割されました。

retrofit new RestAdapter.Builder() .setConverter(new GsonConverter(gson)) ... retrofit2 compile 'com.squareup.retrofit2:converter-gson:2.0.0' new Retrofit.Builder() .addConverterFactory(GsonConverterFactory.create(gson)) ... gson 以外の Converter については square.github.io/retrofit/ の CONVERTERS 部分に記載があります。


RxJava

retrofit で RxJava を使うためのクラスは別のモジュールに分割されました。

retrofit2 compile 'com.squareup.retrofit2:adapter-rxjava:2.0.0' new Retrofit.Builder() .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) ...
AndroidLog

retrofit.android.AndroidLog はなくなりました。 https://github.com/square/okhttp/tree/master/okhttp-logging-interceptor
を使って okhttp3 の interceptor で行います。

retrofit AndroidLog logger = new AndroidLog(MyService.class.getSimpleName()); new RestAdapter.Builder() .setLogLevel(RestAdapter.LogLevel.FULL) .setLog(logger) ... retrofit2 compile 'com.squareup.okhttp3:logging-interceptor:3.0.0' HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); logging.setLevel(HttpLoggingInterceptor.Level.BODY); new OkHttpClient.Builder(); .addInterceptor(logging) ...
ErrorHandler, RetrofitError

retrofit.ErrorHandler, retrofit.RetrofitError はなくなりました。
okhttp3 の interceptor で行うとか https://github.com/square/retrofit/issues/1102
CallAdapterFactory を作るとか https://github.com/square/retrofit/pull/1277/files
いくつか方法があります。

adapter-rxjava を使っているなら HttpException が onError() で返されるので、ここから status code, message, response が取れます。

retrofit new RestAdapter.Builder() .setErrorHandler(new MyErrorHandler()) ... public class MyErrorHandler implements ErrorHandler { @Override public Throwable handleError(RetrofitError cause) { if (cause.getKind() == RetrofitError.Kind.NETWORK) { // network error ... } else if (cause.getResponse() != null) { final int statusCode = cause.getResponse().getStatus(); try { MyErrorResponse errorResponse = (MyErrorResponse) cause.getBodyAs(MyErrorResponse.class); } catch (Exception e) { ... } } else { ... } } } retrofit2 @Override public void onError(Throwable e) { if (e instanceof IOException) { // network error ... } else if (e instanceof HttpException) { HttpException he = (HttpException) e; final int statusCode = he.code(); final ResponseBody errorBody = e.response().errorBody(); if (errorBody != null) { try { MyErrorResponse errorResponse = (MyErrorResponse) GsonConverterFactory .create() .responseBodyConverter(MyErrorResponse.class, new Annotation[0], null) .convert(errorBody); // retrofit インスタンスが利用できる場合 // MyErrorResponse errorResponse = retrofit // .responseConverter(MyErrorResponse.class, new Annotation[0]) // .convert(errorBody); } catch (Exception re) { ... } } } else { ... } }
RequestInterceptor

retrofit.RequestInterceptor はなくなりました。
okhttp3 の interceptor を使います。https://github.com/square/retrofit/issues/1082

retrofit public class MyRequestInterceptor implements RequestInterceptor { @Override public void intercept(RequestFacade request) { request.addHeader(HEADER_KEY, headerValue); } } new RestAdapter.Builder() .setRequestInterceptor(new MyRequestInterceptor()) ... retrofit2 public class MyRequestInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { final Request.Builder builder = chain.request().newBuilder(); builder.addHeader(HEADER_KEY, headerValue); return chain.proceed(builder.build()); } } new OkHttpClient.Builder() .addInterceptor(new MyRequestInterceptor()) ...
Response

import 先を変更する必要があります。

retrofit import retrofit.client.Response retrofit2 import retrofit2.Response
Observable<Response>

ResponseBody を指定する必要があります。

retrofit @DELETE("/delete") Observable<Response> delete(); retrofit2 @DELETE("/delete") Observable<Response<ResponseBody>> delete();
@Body, @GET, @Post など

import 先を変更する必要があります。

retrofit import retrofit.http.Body retrofit2 import retrofit2.http.Body Query の encodeValue は encoded に名前が変わっています。

retrofit @Query(value = "categories", encodeValue = false) @Nullable String categories retrofit2 @Query(value = "categories", encoded = false) @Nullable String categories
TypedFile

TypedFile はなくなりました。
MultipartBody.Part を使います。

retrofit @Multipart @POST("/upload") Observable<Response> uploadImage( @Part("image") TypedFile image ); final TypedFile image = new TypedFile("image/jpeg", file); retrofit2 @Multipart @PATCH("/upload") Observable<Response> uploadImage( @Part() MultipartBody.Part image ); MultipartBody.Part のときは @Part に value(上記だと "image")を設定してはいけません。設定すると実行時に落ちます。 final MultipartBody.Part image = MultipartBody.Part.createFormData("image", file.getName(), RequestBody.create(MediaType.parse("image/jpeg"), file)); もともと @Part に指定していた "image" は MultipartBody.Part.createFormData() の第1引数に指定します。


Client

retrofit.client.Client はなくなりました。

retrofit側でモック化したい場合は retrofit-mock があります。 compile 'com.squareup.retrofit2:retrofit-mock:2.0.0' 使い方は
https://github.com/square/retrofit/blob/master/samples/src/main/java/com/example/retrofit/SimpleMockService.java が参考になります。

okhttp側でモック化したい場合は mockwebserverがあります。 compile 'com.squareup.okhttp3:mockwebserver:3.0.0'


okhttp

okhttp3 を使います。

retrofit import com.squareup.okhttp.Interceptor; import com.squareup.okhttp.Request; import com.squareup.okhttp.Response; retrofit2 import okhttp3.Interceptor; import okhttp3.Request; import okhttp3.Response;
stetho-okhttp

stetho-okhttp3 を使います。https://github.com/facebook/stetho/issues/327

retrofit2 compile 'com.facebook.stetho:stetho-okhttp3:1.3.1'


java.io.InterruptedIOException: thread interrupted

Retrofit2 (というか okhttp3)にして thread interrupted java.io.InterruptedIOException: thread interrupted at okio.Timeout.throwIfReached(Timeout.java:145) at okio.Okio$1.write(Okio.java:77) ... というようなエラーが起きたら、RxJava の subscribeOn() や observeOn() の使い方があやしいので見直しましょう。
例えば以下のような、subscribeOn(Schedulers.newThread()) を指定した複数の Observable を merge して takeLast すると InterruptedIOExceptionになります。 zip や combineLatest だと問題ないです。

NG final List<Observable<ImageResponse>> observables = new ArrayList<>(); for (...) { final MultipartBody.Part image = ...; final Observable<ImageResponse> o = service.uploadImage(image) .subscribeOn(Schedulers.newThread()); observables.add(o); } Observable .merge(observables) .takeLast(1) .subscribeOn(Schedulers.newThread()); .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<ImageResponse>() { ... }); OK final List<Observable<ImageResponse>> observables = new ArrayList<>(); for (...) { final MultipartBody.Part image = ...; final Observable<ImageResponse> o = service.uploadImage(image) .subscribeOn(Schedulers.newThread()); observables.add(o); } Observable .combineLatest(observables, new FuncN<ImageResponse>() { @Override public ImageResponse call(Object... args) { return ...; } }) .subscribeOn(Schedulers.newThread()); .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<ImageResponse>() { ... });


1 件のコメント:

  1. Thanks!!!! I arrived here looking for the "encodeValue" param :·)

    返信削除