Retrofit
Dart向けのタイプセーフHTTPクライアント生成ライブラリ。Dioベースで、source_genを使用してインターフェース定義から自動的にHTTPクライアント実装を生成。Android RetrofitからインスパイアされたAPI設計により、宣言的で保守性の高いAPI通信を実現。
GitHub概要
square/retrofit
A type-safe HTTP client for Android and the JVM
トピックス
スター履歴
ライブラリ
Retrofit
概要
Retrofitは「Android/JVM向けの型安全なHTTPクライアント」として開発された、Java/AndroidエコシステムでRESTful API消費のデファクトスタンダードとして確立している高レベルHTTPクライアントライブラリです。「アノテーションベースのAPI定義」を重視して設計され、Javaインターフェースを使用してHTTP APIを宣言的に定義し、実行時に具象実装を自動生成。型安全性、コンパイル時チェック、自動シリアライゼーション、多様な非同期処理モデル、豊富な拡張性により、複雑なWeb API統合を直感的で保守性の高いコードで実現します。
詳細
Retrofit 2025年版(v2.11系)は、Javaエコシステムにおける成熟したHTTP通信ソリューションとして継続的進化を遂げています。OkHttpをベースとした堅牢な実装により、ServiceMethod、RequestFactory、OkHttpCall、CallAdapter、Converterの5つの主要コンポーネントが連携し、インターフェースメソッド呼び出しをHTTPリクエストに変換。アノテーションベースのAPI設計(@GET、@POST、@PUT等)、多様なConverter(Gson、Moshi、Jackson、Protocol Buffers等)、CallAdapter(RxJava、Kotlin Coroutines、CompletableFuture等)、包括的なエラーハンドリング、マルチパート/フォームデータ対応により、現代的なAndroid/Javaアプリケーション開発の要求を満たします。
主な特徴
- 型安全なAPI定義: コンパイル時型チェックによる高い信頼性とバグ予防
- アノテーションベース設計: 宣言的で直感的なAPI定義による高い可読性
- 自動シリアライゼーション: JSON/XML等の自動変換による開発効率向上
- 多様な非同期モデル: 同期、非同期、RxJava、Kotlin Coroutines対応
- 豊富な拡張性: Converter/CallAdapterによるカスタマイズ性
- OkHttp統合: 高性能HTTP処理と接続プーリング最適化
メリット・デメリット
メリット
- Java/AndroidエコシステムでのRESTクライアントのデファクトスタンダード地位
- アノテーションベースの宣言的API定義による高い開発効率と可読性
- 強力な型安全性とコンパイル時検証による実行時エラー大幅削減
- 豊富なConverter/CallAdapterによる多様なデータ形式・非同期モデル対応
- 充実したドキュメントと豊富なコミュニティサポート
- OkHttpベースの高性能ネットワーク処理と詳細設定可能性
デメリット
- 学習コストと初期設定の複雑さ(アノテーション理解必須)
- リフレクションベースの実装による実行時オーバーヘッド
- Converterやライブラリ依存による依存関係の増加
- 非常に単純なHTTP通信では過度に高機能すぎる場合
- ProGuard/R8環境での設定複雑性(keep rule必要)
- Android Runtime制約による一部Java 8機能制限
参考ページ
書き方の例
インストールと基本セットアップ
// build.gradle (Module: app) - 基本設定
dependencies {
implementation 'com.squareup.retrofit2:retrofit:2.11.0'
implementation 'com.squareup.retrofit2:converter-gson:2.11.0' // JSON変換用
implementation 'com.squareup.okhttp3:logging-interceptor:4.12.0' // ログ用
}
// マルチパート/ファイルアップロード対応
dependencies {
implementation 'com.squareup.retrofit2:retrofit:2.11.0'
implementation 'com.squareup.retrofit2:converter-gson:2.11.0'
implementation 'com.squareup.retrofit2:converter-scalars:2.11.0' // String変換用
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
}
// RxJava統合版
dependencies {
implementation 'com.squareup.retrofit2:retrofit:2.11.0'
implementation 'com.squareup.retrofit2:adapter-rxjava3:2.11.0'
implementation 'io.reactivex.rxjava3:rxjava:3.1.8'
implementation 'io.reactivex.rxjava3:rxandroid:3.0.2'
}
// 基本的なAPIインターフェース定義
import retrofit2.Call;
import retrofit2.http.*;
// ユーザーデータクラス
public class User {
public int id;
public String name;
public String email;
public int age;
public String avatarUrl;
// コンストラクタ、getter/setter省略
}
public class UserCreateRequest {
public String name;
public String email;
public int age;
public UserCreateRequest(String name, String email, int age) {
this.name = name;
this.email = email;
this.age = age;
}
}
// API インターフェース定義
public interface UserApiService {
// GET リクエスト(全ユーザー取得)
@GET("users")
Call<List<User>> getUsers();
// GET リクエスト(特定ユーザー取得)
@GET("users/{id}")
Call<User> getUser(@Path("id") int userId);
// GET リクエスト(クエリパラメータ付き)
@GET("users")
Call<List<User>> getUsersWithQuery(
@Query("page") int page,
@Query("limit") int limit,
@Query("sort") String sort
);
// POST リクエスト(ユーザー作成)
@POST("users")
Call<User> createUser(@Body UserCreateRequest user);
// PUT リクエスト(ユーザー更新)
@PUT("users/{id}")
Call<User> updateUser(
@Path("id") int userId,
@Body UserCreateRequest user
);
// DELETE リクエスト
@DELETE("users/{id}")
Call<Void> deleteUser(@Path("id") int userId);
// ヘッダー付きリクエスト
@GET("users/profile")
Call<User> getProfile(@Header("Authorization") String token);
}
// Retrofit インスタンス作成
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class ApiClient {
private static final String BASE_URL = "https://api.example.com/";
private static Retrofit retrofit;
public static Retrofit getRetrofitInstance() {
if (retrofit == null) {
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
public static UserApiService getUserApiService() {
return getRetrofitInstance().create(UserApiService.class);
}
}
基本的なAPIコール(同期・非同期)
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import android.util.Log;
public class UserRepository {
private UserApiService apiService;
public UserRepository() {
this.apiService = ApiClient.getUserApiService();
}
// 同期実行(メインスレッド以外で実行すること)
public List<User> getUsersSync() {
try {
Call<List<User>> call = apiService.getUsers();
Response<List<User>> response = call.execute();
if (response.isSuccessful()) {
return response.body();
} else {
Log.e("API_ERROR", "Error code: " + response.code());
return null;
}
} catch (IOException e) {
Log.e("NETWORK_ERROR", "Network error", e);
return null;
}
}
// 非同期実行(推奨)
public void getUsersAsync(ApiCallback<List<User>> callback) {
Call<List<User>> call = apiService.getUsers();
call.enqueue(new Callback<List<User>>() {
@Override
public void onResponse(Call<List<User>> call, Response<List<User>> response) {
if (response.isSuccessful()) {
callback.onSuccess(response.body());
} else {
callback.onError("API Error: " + response.code() + " - " + response.message());
}
}
@Override
public void onFailure(Call<List<User>> call, Throwable t) {
callback.onError("Network Error: " + t.getMessage());
}
});
}
// 特定ユーザー取得
public void getUserById(int userId, ApiCallback<User> callback) {
Call<User> call = apiService.getUser(userId);
call.enqueue(new Callback<User>() {
@Override
public void onResponse(Call<User> call, Response<User> response) {
if (response.isSuccessful()) {
User user = response.body();
if (user != null) {
callback.onSuccess(user);
} else {
callback.onError("User not found");
}
} else {
handleErrorResponse(response, callback);
}
}
@Override
public void onFailure(Call<User> call, Throwable t) {
callback.onError("Network error: " + t.getMessage());
}
});
}
// ユーザー作成
public void createUser(String name, String email, int age, ApiCallback<User> callback) {
UserCreateRequest request = new UserCreateRequest(name, email, age);
Call<User> call = apiService.createUser(request);
call.enqueue(new Callback<User>() {
@Override
public void onResponse(Call<User> call, Response<User> response) {
if (response.isSuccessful()) {
callback.onSuccess(response.body());
} else {
handleErrorResponse(response, callback);
}
}
@Override
public void onFailure(Call<User> call, Throwable t) {
callback.onError("Create user failed: " + t.getMessage());
}
});
}
// エラーレスポンス処理
private void handleErrorResponse(Response<?> response, ApiCallback<?> callback) {
try {
String errorBody = response.errorBody().string();
String errorMsg = String.format("HTTP %d: %s - %s",
response.code(), response.message(), errorBody);
callback.onError(errorMsg);
} catch (IOException e) {
callback.onError("HTTP " + response.code() + ": " + response.message());
}
}
}
// コールバックインターフェース
public interface ApiCallback<T> {
void onSuccess(T result);
void onError(String error);
}
// 使用例
public class MainActivity extends AppCompatActivity {
private UserRepository userRepository;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
userRepository = new UserRepository();
// ユーザー一覧取得
userRepository.getUsersAsync(new ApiCallback<List<User>>() {
@Override
public void onSuccess(List<User> users) {
Log.d("API_SUCCESS", "取得したユーザー数: " + users.size());
// UIを更新
updateUserList(users);
}
@Override
public void onError(String error) {
Log.e("API_ERROR", error);
// エラー表示
showError(error);
}
});
}
private void updateUserList(List<User> users) {
// リストビューやRecyclerViewの更新
}
private void showError(String error) {
// エラーダイアログやトーストの表示
Toast.makeText(this, error, Toast.LENGTH_LONG).show();
}
}
高度な設定(認証、インターセプター、タイムアウト)
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import java.util.concurrent.TimeUnit;
public class AdvancedApiClient {
private static final String BASE_URL = "https://api.example.com/";
private static Retrofit retrofit;
// 高度なRetrofitインスタンス作成
public static Retrofit getAdvancedRetrofitInstance() {
if (retrofit == null) {
// HTTP ログインターセプター
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
// 認証インターセプター
Interceptor authInterceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
// 認証トークン取得(SharedPreferencesやSecureStorageから)
String token = getAuthToken();
if (token != null) {
Request authenticatedRequest = originalRequest.newBuilder()
.header("Authorization", "Bearer " + token)
.header("Accept", "application/json")
.header("Content-Type", "application/json")
.build();
return chain.proceed(authenticatedRequest);
}
return chain.proceed(originalRequest);
}
};
// リトライインターセプター
Interceptor retryInterceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = null;
IOException exception = null;
int maxRetries = 3;
for (int i = 0; i < maxRetries; i++) {
try {
response = chain.proceed(request);
if (response.isSuccessful()) {
return response;
}
// サーバーエラーの場合リトライ
if (response.code() >= 500) {
response.close();
Thread.sleep(1000 * (i + 1)); // 指数バックオフ
continue;
}
return response;
} catch (IOException e) {
exception = e;
if (i == maxRetries - 1) break;
try {
Thread.sleep(1000 * (i + 1));
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new IOException("Interrupted during retry", ie);
}
}
}
if (exception != null) throw exception;
return response;
}
};
// OkHttpClient設定
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS) // 接続タイムアウト
.readTimeout(30, TimeUnit.SECONDS) // 読み取りタイムアウト
.writeTimeout(30, TimeUnit.SECONDS) // 書き込みタイムアウト
.addInterceptor(authInterceptor) // 認証
.addInterceptor(retryInterceptor) // リトライ
.addInterceptor(loggingInterceptor) // ログ(デバッグ時のみ)
.build();
// Retrofit設定
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(createCustomGson()))
.build();
}
return retrofit;
}
// カスタムGson設定
private static Gson createCustomGson() {
return new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.setDateFormat("yyyy-MM-dd HH:mm:ss")
.excludeFieldsWithoutExposeAnnotation()
.create();
}
// 認証トークン取得
private static String getAuthToken() {
// SharedPreferencesやSecureStorageから取得
// 実装は環境に依存
return "your-auth-token";
}
}
// SSL証明書ピンニング
public class SSLPinnedApiClient {
public static Retrofit createSSLPinnedRetrofit() {
CertificatePinner certificatePinner = new CertificatePinner.Builder()
.add("api.example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.build();
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build();
return new Retrofit.Builder()
.baseUrl("https://api.example.com/")
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
}
// 動的ベースURL設定
public interface DynamicApiService {
@GET
Call<User> getUser(@Url String fullUrl);
@GET("users/{id}")
Call<User> getUserWithDynamicBase(@Path("id") int userId);
}
// プロキシ設定
public class ProxyApiClient {
public static Retrofit createProxyRetrofit() {
Proxy proxy = new Proxy(Proxy.Type.HTTP,
new InetSocketAddress("proxy.example.com", 8080));
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.proxy(proxy)
.build();
return new Retrofit.Builder()
.baseUrl("https://api.example.com/")
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
}
ファイルアップロード・マルチパートフォーム
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import java.io.File;
// ファイルアップロード用APIインターフェース
public interface FileUploadService {
// 単一ファイルアップロード
@Multipart
@POST("upload/single")
Call<UploadResponse> uploadSingleFile(
@Part("description") RequestBody description,
@Part MultipartBody.Part file
);
// 複数ファイルアップロード
@Multipart
@POST("upload/multiple")
Call<UploadResponse> uploadMultipleFiles(
@Part("title") RequestBody title,
@Part("category") RequestBody category,
@Part List<MultipartBody.Part> files
);
// プロフィール更新(画像+データ)
@Multipart
@PUT("users/{id}/profile")
Call<User> updateProfile(
@Path("id") int userId,
@Part("name") RequestBody name,
@Part("email") RequestBody email,
@Part MultipartBody.Part avatar
);
// フォームデータ送信
@FormUrlEncoded
@POST("users/register")
Call<User> registerUser(
@Field("username") String username,
@Field("email") String email,
@Field("password") String password,
@Field("age") int age
);
// ファイルダウンロード
@GET("files/{fileId}/download")
Call<ResponseBody> downloadFile(@Path("fileId") String fileId);
}
public class UploadResponse {
public String fileId;
public String fileName;
public long fileSize;
public String downloadUrl;
public String message;
}
public class FileUploadRepository {
private FileUploadService uploadService;
public FileUploadRepository() {
this.uploadService = ApiClient.getRetrofitInstance()
.create(FileUploadService.class);
}
// 単一ファイルアップロード
public void uploadSingleFile(File file, String description,
ApiCallback<UploadResponse> callback) {
// ファイルをRequestBodyに変換
RequestBody requestFile = RequestBody.create(
MediaType.parse("multipart/form-data"), file);
// MultipartBody.Part作成
MultipartBody.Part filePart = MultipartBody.Part.createFormData(
"file", file.getName(), requestFile);
// 説明文をRequestBodyに変換
RequestBody descriptionBody = RequestBody.create(
MediaType.parse("multipart/form-data"), description);
Call<UploadResponse> call = uploadService.uploadSingleFile(descriptionBody, filePart);
call.enqueue(new Callback<UploadResponse>() {
@Override
public void onResponse(Call<UploadResponse> call, Response<UploadResponse> response) {
if (response.isSuccessful()) {
callback.onSuccess(response.body());
} else {
callback.onError("Upload failed: " + response.code());
}
}
@Override
public void onFailure(Call<UploadResponse> call, Throwable t) {
callback.onError("Upload error: " + t.getMessage());
}
});
}
// 複数ファイルアップロード
public void uploadMultipleFiles(List<File> files, String title, String category,
ApiCallback<UploadResponse> callback) {
List<MultipartBody.Part> fileParts = new ArrayList<>();
for (File file : files) {
RequestBody requestFile = RequestBody.create(
MediaType.parse("multipart/form-data"), file);
MultipartBody.Part filePart = MultipartBody.Part.createFormData(
"files", file.getName(), requestFile);
fileParts.add(filePart);
}
RequestBody titleBody = RequestBody.create(
MediaType.parse("multipart/form-data"), title);
RequestBody categoryBody = RequestBody.create(
MediaType.parse("multipart/form-data"), category);
Call<UploadResponse> call = uploadService.uploadMultipleFiles(
titleBody, categoryBody, fileParts);
call.enqueue(new Callback<UploadResponse>() {
@Override
public void onResponse(Call<UploadResponse> call, Response<UploadResponse> response) {
if (response.isSuccessful()) {
callback.onSuccess(response.body());
} else {
callback.onError("Multiple upload failed: " + response.code());
}
}
@Override
public void onFailure(Call<UploadResponse> call, Throwable t) {
callback.onError("Multiple upload error: " + t.getMessage());
}
});
}
// プロフィール画像アップロード
public void updateProfileWithAvatar(int userId, String name, String email,
File avatarFile, ApiCallback<User> callback) {
RequestBody nameBody = RequestBody.create(
MediaType.parse("multipart/form-data"), name);
RequestBody emailBody = RequestBody.create(
MediaType.parse("multipart/form-data"), email);
RequestBody avatarRequestBody = RequestBody.create(
MediaType.parse("image/*"), avatarFile);
MultipartBody.Part avatarPart = MultipartBody.Part.createFormData(
"avatar", avatarFile.getName(), avatarRequestBody);
Call<User> call = uploadService.updateProfile(userId, nameBody, emailBody, avatarPart);
call.enqueue(new Callback<User>() {
@Override
public void onResponse(Call<User> call, Response<User> response) {
if (response.isSuccessful()) {
callback.onSuccess(response.body());
} else {
callback.onError("Profile update failed: " + response.code());
}
}
@Override
public void onFailure(Call<User> call, Throwable t) {
callback.onError("Profile update error: " + t.getMessage());
}
});
}
// ファイルダウンロード
public void downloadFile(String fileId, File destinationFile,
DownloadCallback callback) {
Call<ResponseBody> call = uploadService.downloadFile(fileId);
call.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
if (response.isSuccessful()) {
// バックグラウンドでファイル保存
new Thread(() -> {
try {
saveFileFromResponseBody(response.body(), destinationFile);
callback.onDownloadComplete(destinationFile);
} catch (IOException e) {
callback.onDownloadError("File save error: " + e.getMessage());
}
}).start();
} else {
callback.onDownloadError("Download failed: " + response.code());
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
callback.onDownloadError("Download error: " + t.getMessage());
}
});
}
private void saveFileFromResponseBody(ResponseBody body, File destinationFile) throws IOException {
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(destinationFile);
while (true) {
int read = inputStream.read(fileReader);
if (read == -1) break;
outputStream.write(fileReader, 0, read);
fileSizeDownloaded += read;
Log.d("DOWNLOAD", "Downloaded: " + fileSizeDownloaded + " / " + fileSize);
}
outputStream.flush();
} finally {
if (inputStream != null) inputStream.close();
if (outputStream != null) outputStream.close();
}
}
}
public interface DownloadCallback {
void onDownloadComplete(File file);
void onDownloadError(String error);
}
エラーハンドリングと詳細レスポンス処理
import retrofit2.HttpException;
import com.google.gson.JsonSyntaxException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
// カスタムエラーレスポンス
public class ApiErrorResponse {
public int code;
public String message;
public String details;
public List<FieldError> fieldErrors;
public static class FieldError {
public String field;
public String message;
}
}
// エラーハンドリング用ユーティリティ
public class ApiErrorHandler {
public static String handleError(Throwable throwable) {
if (throwable instanceof HttpException) {
return handleHttpException((HttpException) throwable);
} else if (throwable instanceof SocketTimeoutException) {
return "接続がタイムアウトしました。ネットワーク状況を確認してください。";
} else if (throwable instanceof UnknownHostException) {
return "サーバーに接続できません。インターネット接続を確認してください。";
} else if (throwable instanceof JsonSyntaxException) {
return "サーバーからの応答形式が正しくありません。";
} else if (throwable instanceof IOException) {
return "ネットワークエラーが発生しました: " + throwable.getMessage();
} else {
return "予期しないエラーが発生しました: " + throwable.getMessage();
}
}
private static String handleHttpException(HttpException exception) {
int code = exception.code();
try {
String errorBody = exception.response().errorBody().string();
Gson gson = new Gson();
ApiErrorResponse errorResponse = gson.fromJson(errorBody, ApiErrorResponse.class);
if (errorResponse != null && errorResponse.message != null) {
return errorResponse.message;
}
} catch (Exception e) {
// エラーレスポンスの解析に失敗した場合のフォールバック
}
switch (code) {
case 400:
return "リクエストが不正です。入力内容を確認してください。";
case 401:
return "認証が必要です。ログインしてください。";
case 403:
return "この操作を実行する権限がありません。";
case 404:
return "要求されたリソースが見つかりません。";
case 409:
return "データが競合しています。最新の情報を取得してください。";
case 422:
return "入力データに問題があります。内容を確認してください。";
case 429:
return "リクエスト制限に達しました。しばらく時間をおいて再度お試しください。";
case 500:
return "サーバー内部エラーが発生しました。しばらく時間をおいて再度お試しください。";
case 502:
return "サーバーが一時的に利用できません。";
case 503:
return "サービスが一時的に利用できません。メンテナンス中の可能性があります。";
default:
return "HTTP エラー " + code + " が発生しました。";
}
}
}
// 詳細なレスポンス処理
public class DetailedApiRepository {
private UserApiService apiService;
public DetailedApiRepository() {
this.apiService = ApiClient.getRetrofitInstance().create(UserApiService.class);
}
// 詳細なレスポンス情報を含むコールバック
public interface DetailedApiCallback<T> {
void onSuccess(T result, Response<T> response);
void onError(String error, int httpCode, Response<T> response);
void onNetworkError(String error);
}
public void getUserWithDetailedResponse(int userId, DetailedApiCallback<User> callback) {
Call<User> call = apiService.getUser(userId);
call.enqueue(new Callback<User>() {
@Override
public void onResponse(Call<User> call, Response<User> response) {
if (response.isSuccessful()) {
User user = response.body();
// レスポンスヘッダーの確認
String rateLimit = response.headers().get("X-RateLimit-Remaining");
String serverTime = response.headers().get("Date");
Log.d("API_INFO", "Rate limit remaining: " + rateLimit);
Log.d("API_INFO", "Server time: " + serverTime);
Log.d("API_INFO", "Response time: " + (System.currentTimeMillis() - call.request().tag()));
callback.onSuccess(user, response);
} else {
// エラーレスポンスの詳細処理
try {
String errorBody = response.errorBody().string();
Log.e("API_ERROR", "Error body: " + errorBody);
Gson gson = new Gson();
ApiErrorResponse errorResponse = gson.fromJson(errorBody, ApiErrorResponse.class);
String errorMessage = (errorResponse != null && errorResponse.message != null)
? errorResponse.message
: ApiErrorHandler.handleError(new HttpException(response));
callback.onError(errorMessage, response.code(), response);
} catch (IOException e) {
callback.onError("エラーレスポンスの解析に失敗しました", response.code(), response);
}
}
}
@Override
public void onFailure(Call<User> call, Throwable t) {
String errorMessage = ApiErrorHandler.handleError(t);
callback.onNetworkError(errorMessage);
}
});
}
// キャンセル可能なAPIコール
public Call<List<User>> getUsersWithCancellation(ApiCallback<List<User>> callback) {
Call<List<User>> call = apiService.getUsers();
call.enqueue(new Callback<List<User>>() {
@Override
public void onResponse(Call<List<User>> call, Response<List<User>> response) {
if (!call.isCanceled()) {
if (response.isSuccessful()) {
callback.onSuccess(response.body());
} else {
callback.onError("API Error: " + response.code());
}
}
}
@Override
public void onFailure(Call<List<User>> call, Throwable t) {
if (!call.isCanceled()) {
callback.onError(ApiErrorHandler.handleError(t));
}
}
});
return call; // 呼び出し元でキャンセル可能
}
}
// 使用例:キャンセル機能付きAPI呼び出し
public class CancellableApiActivity extends AppCompatActivity {
private Call<List<User>> currentCall;
private DetailedApiRepository repository;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
repository = new DetailedApiRepository();
// キャンセル可能なAPI呼び出し
currentCall = repository.getUsersWithCancellation(new ApiCallback<List<User>>() {
@Override
public void onSuccess(List<User> users) {
// UI更新
updateUserList(users);
}
@Override
public void onError(String error) {
// エラー表示
showError(error);
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
// アクティビティ破棄時にAPIコールをキャンセル
if (currentCall != null && !currentCall.isCanceled()) {
currentCall.cancel();
}
}
private void updateUserList(List<User> users) {
// リスト更新処理
}
private void showError(String error) {
Toast.makeText(this, error, Toast.LENGTH_LONG).show();
}
}
RxJava/Kotlin Coroutines統合
// RxJava統合
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.schedulers.Schedulers;
// RxJava用APIインターフェース
public interface RxUserApiService {
@GET("users")
Observable<List<User>> getUsers();
@GET("users/{id}")
Single<User> getUser(@Path("id") int userId);
@POST("users")
Single<User> createUser(@Body UserCreateRequest user);
}
// RxJava対応Retrofitクライアント
public class RxApiClient {
private static Retrofit retrofit;
public static Retrofit getRxRetrofitInstance() {
if (retrofit == null) {
retrofit = new Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
.build();
}
return retrofit;
}
public static RxUserApiService getRxUserApiService() {
return getRxRetrofitInstance().create(RxUserApiService.class);
}
}
public class RxUserRepository {
private RxUserApiService apiService;
public RxUserRepository() {
this.apiService = RxApiClient.getRxUserApiService();
}
// RxJava を使用したAPI呼び出し
public void loadUsersWithRx() {
apiService.getUsers()
.subscribeOn(Schedulers.io()) // バックグラウンドスレッドで実行
.observeOn(AndroidSchedulers.mainThread()) // メインスレッドで結果を受け取る
.subscribe(
users -> {
// 成功時の処理
Log.d("RX_SUCCESS", "取得したユーザー数: " + users.size());
},
throwable -> {
// エラー時の処理
String error = ApiErrorHandler.handleError(throwable);
Log.e("RX_ERROR", error);
}
);
}
// 複数API呼び出しの組み合わせ
public void loadUserWithDetails(int userId) {
Single.zip(
apiService.getUser(userId),
apiService.getUserPosts(userId),
apiService.getUserFollowers(userId),
(user, posts, followers) -> {
// 3つのAPIレスポンスを組み合わせ
UserWithDetails userDetails = new UserWithDetails();
userDetails.user = user;
userDetails.posts = posts;
userDetails.followers = followers;
return userDetails;
}
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
userDetails -> {
// 統合されたデータの処理
Log.d("RX_SUCCESS", "ユーザー詳細取得完了");
},
throwable -> {
Log.e("RX_ERROR", ApiErrorHandler.handleError(throwable));
}
);
}
}
// Kotlin Coroutines統合
import kotlinx.coroutines.*
// Kotlin Suspend Function対応APIインターフェース
interface CoroutineUserApiService {
@GET("users")
suspend fun getUsers(): List<User>
@GET("users/{id}")
suspend fun getUser(@Path("id") userId: Int): User
@POST("users")
suspend fun createUser(@Body user: UserCreateRequest): User
@PUT("users/{id}")
suspend fun updateUser(@Path("id") userId: Int, @Body user: UserCreateRequest): User
@DELETE("users/{id}")
suspend fun deleteUser(@Path("id") userId: Int): Response<Void>
}
class CoroutineUserRepository {
private val apiService = ApiClient.getRetrofitInstance()
.create(CoroutineUserApiService::class.java)
// Coroutineを使用したAPI呼び出し
suspend fun getUsers(): Result<List<User>> {
return try {
val users = apiService.getUsers()
Result.success(users)
} catch (e: Exception) {
val errorMessage = ApiErrorHandler.handleError(e)
Result.failure(Exception(errorMessage))
}
}
suspend fun getUserById(userId: Int): Result<User> {
return try {
val user = apiService.getUser(userId)
Result.success(user)
} catch (e: Exception) {
Result.failure(e)
}
}
// 複数のAPI呼び出しを並行実行
suspend fun getUserWithDetailsParallel(userId: Int): Result<UserWithDetails> {
return try {
val userDeferred = async { apiService.getUser(userId) }
val postsDeferred = async { apiService.getUserPosts(userId) }
val followersDeferred = async { apiService.getUserFollowers(userId) }
val user = userDeferred.await()
val posts = postsDeferred.await()
val followers = followersDeferred.await()
val userDetails = UserWithDetails(user, posts, followers)
Result.success(userDetails)
} catch (e: Exception) {
Result.failure(e)
}
}
}
// Activity/FragmentでのCoroutine使用例
class CoroutineActivity : AppCompatActivity() {
private val repository = CoroutineUserRepository()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ライフサイクルスコープでCoroutine実行
lifecycleScope.launch {
loadUsers()
}
}
private suspend fun loadUsers() {
try {
// ローディング表示
showLoading(true)
// API呼び出し
val result = repository.getUsers()
result.fold(
onSuccess = { users ->
// 成功時の処理
updateUserList(users)
showLoading(false)
},
onFailure = { exception ->
// エラー時の処理
showError(exception.message ?: "Unknown error")
showLoading(false)
}
)
} catch (e: Exception) {
showError("Unexpected error: ${e.message}")
showLoading(false)
}
}
private fun updateUserList(users: List<User>) {
// UI更新処理
}
private fun showLoading(show: Boolean) {
// ローディング表示/非表示
}
private fun showError(message: String) {
// エラー表示
}
}