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 になっています。
  1. compile 'com.squareup.retrofit2:retrofit:2.0.0'  

Converter

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

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


RxJava

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

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

AndroidLog

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

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

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
  1. new RestAdapter.Builder()  
  2.     .setErrorHandler(new MyErrorHandler())  
  3.     ...  
  1. public class MyErrorHandler implements ErrorHandler {  
  2.     @Override  
  3.     public Throwable handleError(RetrofitError cause) {  
  4.         if (cause.getKind() == RetrofitError.Kind.NETWORK) {    
  5.             // network error  
  6.             ...  
  7.         } else if (cause.getResponse() != null) {    
  8.             final int statusCode = cause.getResponse().getStatus();    
  9.             try {    
  10.                 MyErrorResponse errorResponse =  
  11.                     (MyErrorResponse) cause.getBodyAs(MyErrorResponse.class);    
  12.             } catch (Exception e) {    
  13.                 ...  
  14.             }    
  15.         } else {  
  16.             ...  
  17.         }  
  18.     }  
  19. }  
retrofit2
  1.     @Override  
  2.     public void onError(Throwable e) {  
  3.         if (e instanceof IOException) {  
  4.             // network error  
  5.             ...  
  6.         } else if (e instanceof HttpException) {  
  7.             HttpException he = (HttpException) e;  
  8.             final int statusCode = he.code();  
  9.             final ResponseBody errorBody = e.response().errorBody();  
  10.             if (errorBody != null) {  
  11.                 try {  
  12.                     MyErrorResponse errorResponse =   
  13.                         (MyErrorResponse) GsonConverterFactory  
  14.                             .create()  
  15.                             .responseBodyConverter(MyErrorResponse.class,   
  16.                                 new Annotation[0], null)  
  17.                             .convert(errorBody);  
  18.    
  19.                 // retrofit インスタンスが利用できる場合  
  20. //                MyErrorResponse errorResponse = retrofit  
  21. //                        .responseConverter(MyErrorResponse.class, new Annotation[0])  
  22. //                        .convert(errorBody);  
  23.   
  24.                 } catch (Exception re) {  
  25.                     ...  
  26.                 }  
  27.             }  
  28.         } else {  
  29.             ...  
  30.         }  
  31.     }  

RequestInterceptor

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

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

Response

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

retrofit
  1. import retrofit.client.Response  
retrofit2
  1. import retrofit2.Response  

Observable<Response>

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

retrofit
  1. @DELETE("/delete")  
  2. Observable<Response> delete();  
retrofit2
  1. @DELETE("/delete")  
  2. Observable<Response<ResponseBody>> delete();  

@Body, @GET, @Post など

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

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

retrofit
  1. @Query(value = "categories", encodeValue = false@Nullable String categories  
retrofit2
  1. @Query(value = "categories", encoded = false@Nullable String categories  

TypedFile

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

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


Client

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

retrofit側でモック化したい場合は retrofit-mock があります。
  1. 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があります。
  1. compile 'com.squareup.okhttp3:mockwebserver:3.0.0'  



okhttp

okhttp3 を使います。

retrofit
  1. import com.squareup.okhttp.Interceptor;    
  2. import com.squareup.okhttp.Request;    
  3. import com.squareup.okhttp.Response;  
retrofit2
  1. import okhttp3.Interceptor;  
  2. import okhttp3.Request;  
  3. import okhttp3.Response;  

stetho-okhttp

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

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



java.io.InterruptedIOException: thread interrupted

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

NG
  1. final List<Observable<ImageResponse>> observables = new ArrayList<>();  
  2. for (...) {  
  3.     final MultipartBody.Part image = ...;  
  4.     final Observable<ImageResponse> o = service.uploadImage(image)  
  5.             .subscribeOn(Schedulers.newThread());  
  6.     observables.add(o);  
  7. }  
  8.   
  9. Observable  
  10.         .merge(observables)  
  11.         .takeLast(1)  
  12.         .subscribeOn(Schedulers.newThread());  
  13.         .observeOn(AndroidSchedulers.mainThread())  
  14.         .subscribe(new Subscriber<ImageResponse>() {  
  15.                 ...  
  16.         });  
OK
  1. final List<Observable<ImageResponse>> observables = new ArrayList<>();  
  2. for (...) {  
  3.     final MultipartBody.Part image = ...;  
  4.     final Observable<ImageResponse> o = service.uploadImage(image)  
  5.             .subscribeOn(Schedulers.newThread());  
  6.     observables.add(o);  
  7. }  
  8.   
  9. Observable  
  10.         .combineLatest(observables, new FuncN<ImageResponse>() {  
  11.                 @Override  
  12.                 public ImageResponse call(Object... args) {  
  13.                     return ...;  
  14.                 }  
  15.         })  
  16.         .subscribeOn(Schedulers.newThread());  
  17.         .observeOn(AndroidSchedulers.mainThread())  
  18.         .subscribe(new Subscriber<ImageResponse>() {  
  19.                 ...  
  20.         });  



1 件のコメント:

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

    返信削除