Retrofit

Square社開発のタイプセーフなHTTPクライアント。REST APIを宣言的インターフェースに変換し、アノテーションベースでAPIコールを定義。GsonやMoshi等のシリアライゼーションライブラリと統合し、RxJavaやKotlin Coroutinesをサポート。

概要

Retrofitは、Square社が開発したJavaとAndroid向けのタイプセーフなHTTPクライアントライブラリです。RESTful APIとの通信を簡素化し、インターフェースとアノテーションを使用してAPIエンドポイントを定義できます。

主な特徴

  • タイプセーフなAPI定義: インターフェースとアノテーションベースの設計
  • 自動シリアライゼーション: GsonやMoshiなどの各種コンバーターをサポート
  • 非同期処理サポート: RxJava、Kotlin Coroutinesとの統合
  • OkHttpベース: 強力なHTTPクライアントエンジン
  • 拡張性: カスタムコンバーターやアダプターの実装が可能
  • インターセプター: リクエスト/レスポンスの加工や認証処理

インストール

Gradle

dependencies {
    // Retrofit 3.0 (最新版)
    implementation 'com.squareup.retrofit2:retrofit:3.0.0'
    // Gsonコンバーター
    implementation 'com.squareup.retrofit2:converter-gson:3.0.0'
    // RxJavaアダプター(オプション)
    implementation 'com.squareup.retrofit2:adapter-rxjava3:3.0.0'
}

Maven

<dependency>
    <groupId>com.squareup.retrofit2</groupId>
    <artifactId>retrofit</artifactId>
    <version>3.0.0</version>
</dependency>

基本的な使い方

APIインターフェースの定義

public interface GitHubService {
    @GET("users/{user}/repos")
    Call<List<Repo>> listRepos(@Path("user") String user);
    
    @GET("user")
    Call<User> getUser(@Header("Authorization") String token);
}

Retrofitインスタンスの作成

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .addConverterFactory(GsonConverterFactory.create())
    .build();

GitHubService service = retrofit.create(GitHubService.class);

実装例

1. GETリクエスト

public interface UserService {
    // シンプルなGETリクエスト
    @GET("users/{id}")
    Call<User> getUser(@Path("id") long userId);
    
    // クエリパラメータ付き
    @GET("users")
    Call<List<User>> getUsers(
        @Query("page") int page,
        @Query("per_page") int perPage
    );
    
    // 複数のクエリパラメータ
    @GET("search/repositories")
    Call<SearchResult> searchRepos(@QueryMap Map<String, String> options);
}

// 使用例
Call<User> call = userService.getUser(123);
call.enqueue(new Callback<User>() {
    @Override
    public void onResponse(Call<User> call, Response<User> response) {
        if (response.isSuccessful()) {
            User user = response.body();
            // ユーザー情報を処理
        }
    }
    
    @Override
    public void onFailure(Call<User> call, Throwable t) {
        // エラー処理
    }
});

2. POSTリクエスト

public interface ApiService {
    // JSONボディ
    @POST("users")
    Call<User> createUser(@Body User user);
    
    // フォームエンコード
    @FormUrlEncoded
    @POST("user/edit")
    Call<User> updateUser(
        @Field("first_name") String firstName,
        @Field("last_name") String lastName
    );
    
    // マルチパート
    @Multipart
    @POST("upload")
    Call<ResponseBody> uploadImage(
        @Part MultipartBody.Part image,
        @Part("description") RequestBody description
    );
}

// 使用例:JSON送信
User newUser = new User("John", "Doe");
Call<User> call = apiService.createUser(newUser);
Response<User> response = call.execute(); // 同期実行

3. 認証処理

// インターセプターを使用した認証
public class AuthInterceptor implements Interceptor {
    private String authToken;
    
    public AuthInterceptor(String token) {
        this.authToken = token;
    }
    
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request original = chain.request();
        
        Request.Builder builder = original.newBuilder()
            .header("Authorization", "Bearer " + authToken);
            
        Request request = builder.build();
        return chain.proceed(request);
    }
}

// OkHttpクライアントに追加
OkHttpClient client = new OkHttpClient.Builder()
    .addInterceptor(new AuthInterceptor(authToken))
    .build();

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.example.com/")
    .client(client)
    .addConverterFactory(GsonConverterFactory.create())
    .build();

4. エラーハンドリング

public class ApiClient {
    public void handleApiCall() {
        Call<User> call = service.getUser(userId);
        
        call.enqueue(new Callback<User>() {
            @Override
            public void onResponse(Call<User> call, Response<User> response) {
                if (response.isSuccessful()) {
                    User user = response.body();
                    // 成功処理
                } else {
                    // HTTPエラー処理
                    try {
                        ErrorResponse error = gson.fromJson(
                            response.errorBody().string(),
                            ErrorResponse.class
                        );
                        handleError(error);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            
            @Override
            public void onFailure(Call<User> call, Throwable t) {
                if (t instanceof SocketTimeoutException) {
                    // タイムアウト処理
                } else if (t instanceof IOException) {
                    // ネットワークエラー処理
                } else {
                    // その他のエラー処理
                }
            }
        });
    }
}

5. ファイルアップロード

public interface FileUploadService {
    @Multipart
    @POST("upload")
    Call<UploadResponse> uploadFile(
        @Part MultipartBody.Part file,
        @Part("description") RequestBody description
    );
    
    @Multipart
    @POST("upload/multiple")
    Call<UploadResponse> uploadMultipleFiles(
        @Part List<MultipartBody.Part> files
    );
}

// 使用例
File file = new File(filePath);
RequestBody requestFile = RequestBody.create(
    MediaType.parse("image/*"),
    file
);

MultipartBody.Part body = MultipartBody.Part.createFormData(
    "file",
    file.getName(),
    requestFile
);

RequestBody description = RequestBody.create(
    MediaType.parse("text/plain"),
    "画像の説明"
);

Call<UploadResponse> call = service.uploadFile(body, description);

6. Kotlin Coroutinesの使用(Retrofit 3.0)

interface ApiService {
    @GET("posts")
    suspend fun getPosts(): List<Post>
    
    @POST("posts")
    suspend fun createPost(@Body post: Post): Post
}

// ViewModelでの使用
class PostViewModel : ViewModel() {
    private val apiService = RetrofitClient.apiService
    
    fun loadPosts() {
        viewModelScope.launch {
            try {
                val posts = apiService.getPosts()
                // UIを更新
            } catch (e: HttpException) {
                // HTTPエラー処理
                val code = e.code()
                val message = e.message()
            } catch (e: Exception) {
                // その他のエラー処理
            }
        }
    }
}

7. 進捗状況の監視

public interface DownloadService {
    @Streaming
    @GET
    Call<ResponseBody> downloadFile(@Url String fileUrl);
}

// プログレス付きダウンロード
public void downloadFileWithProgress(String fileUrl) {
    Call<ResponseBody> call = service.downloadFile(fileUrl);
    
    call.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
            if (response.isSuccessful()) {
                new Thread(() -> {
                    writeResponseBodyToDisk(response.body());
                }).start();
            }
        }
        
        @Override
        public void onFailure(Call<ResponseBody> call, Throwable t) {
            // エラー処理
        }
    });
}

private boolean writeResponseBodyToDisk(ResponseBody body) {
    try {
        File file = new File(getExternalFilesDir(null) + File.separator + "download.file");
        
        InputStream inputStream = null;
        OutputStream outputStream = null;
        
        try {
            byte[] fileReader = new byte[4096];
            long fileSize = body.contentLength();
            long fileSizeDownloaded = 0;
            
            inputStream = body.byteStream();
            outputStream = new FileOutputStream(file);
            
            while (true) {
                int read = inputStream.read(fileReader);
                if (read == -1) break;
                
                outputStream.write(fileReader, 0, read);
                fileSizeDownloaded += read;
                
                // 進捗を計算してUIを更新
                int progress = (int) ((fileSizeDownloaded * 100) / fileSize);
                publishProgress(progress);
            }
            
            outputStream.flush();
            return true;
        } catch (IOException e) {
            return false;
        } finally {
            if (inputStream != null) inputStream.close();
            if (outputStream != null) outputStream.close();
        }
    } catch (IOException e) {
        return false;
    }
}

他のライブラリとの比較

Retrofit vs OkHttp

  • Retrofit: 高レベルなRESTクライアント、アノテーションベース
  • OkHttp: 低レベルなHTTPクライアント、より細かい制御が可能

Retrofit vs Volley

  • Retrofit: タイプセーフ、RxJava/Coroutinesサポート、大規模アプリ向け
  • Volley: Googleが開発、画像処理に強い、小規模アプリ向け

Retrofit vs Fuel

  • Retrofit: Java/Android標準、豊富なエコシステム
  • Fuel: Kotlin専用、よりシンプルなAPI

ベストプラクティス

  1. シングルトンパターンの使用

    public class RetrofitClient {
        private static Retrofit retrofit = null;
        
        public static Retrofit getClient(String baseUrl) {
            if (retrofit == null) {
                retrofit = new Retrofit.Builder()
                    .baseUrl(baseUrl)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
            }
            return retrofit;
        }
    }
    
  2. タイムアウトの設定

    OkHttpClient client = new OkHttpClient.Builder()
        .connectTimeout(30, TimeUnit.SECONDS)
        .readTimeout(30, TimeUnit.SECONDS)
        .writeTimeout(30, TimeUnit.SECONDS)
        .build();
    
  3. ログ出力の追加

    HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
    logging.setLevel(HttpLoggingInterceptor.Level.BODY);
    
    OkHttpClient client = new OkHttpClient.Builder()
        .addInterceptor(logging)
        .build();
    
  4. エラーレスポンスの統一処理

    public class ApiError {
        private int statusCode;
        private String message;
        
        public static ApiError parseError(Response<?> response) {
            Converter<ResponseBody, ApiError> converter = 
                retrofit.responseBodyConverter(ApiError.class, new Annotation[0]);
                
            ApiError error;
            try {
                error = converter.convert(response.errorBody());
            } catch (IOException e) {
                return new ApiError();
            }
            return error;
        }
    }
    

まとめ

Retrofitは、JavaとAndroidにおけるHTTP通信の標準的なライブラリとして広く採用されています。タイプセーフなAPI定義、豊富な機能、そして優れた拡張性により、小規模から大規模まで様々なアプリケーションで活用できます。特に、RESTful APIとの通信が多いアプリケーションでは、その真価を発揮します。