1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[2025年最新] Android開発でのKtor HTTPクライアント徹底解説:Retrofitとの比較

Posted at

はじめに

こんにちは!Android開発でHTTP通信といえば、長らくRetrofit一択でしたよね。私もずっとRetrofitを使ってきましたが、最近Ktorという選択肢に出会って、「これはすごい!」と感動しています😊

Ktorは、JetBrainsが開発したKotlinベースのHTTPクライアント・サーバーフレームワークです。最初は「また新しいライブラリか...」と思っていましたが、特にKotlin Multiplatformプロジェクトでは本当にその真価を発揮するんです!

「Retrofitじゃだめなの?」「学習コストどうなの?」そんな疑問を持ってる方も多いと思います。この記事では、私が実際に使ってみた経験をもとに、2025年最新の情報に基づいて、Android開発でのKtor HTTPクライアントの使い方を詳しく解説していきます。

Ktorの魅力的な特徴

⚡ 非同期処理への完全対応
Coroutineベースで設計されており、Retrofitよりも自然な非同期処理が書けます。

🎯 軽量設計
必要な機能だけを選択して追加できるプラグイン方式。「重たいライブラリは嫌だ」という方にピッタリ!

🌐 マルチプラットフォーム対応
Kotlin Multiplatformプロジェクトで同じHTTP通信コードを共有できるんです!JVM、Android、JavaScript、Nativeで動作。

🔧 Kotlinファースト
Kotlinの機能をフル活用して設計されているので、Kotlinらしいコードが書けます。

🔄 柔軟性
クライアント・サーバー両方で使用可能。

🆚 Retrofitとの比較

項目 Ktor Retrofit
言語 Kotlin Java (Kotlin対応)
学習コスト やや高い 低い
設定の自由度 高い 中程度
アノテーション 不要 必要
Kotlin Multiplatform対応

新規プロジェクトなら迷わずKtor、既存プロジェクトなら慎重に検討が良いかと思います。

基本的なHttpClientの作成

まずは一番シンプルな形から:

val client = HttpClient(Android)

「え?これだけ?」と最初は拍子抜けしました😅
シンプルなクライアントも良いですが、実際のアプリでは色々な設定が必要ですよね。Ktorの真価はここから発揮されます!

HttpClientの設定

class NetworkClient {
    private val client = HttpClient(Android) {
        install(ContentNegotiation) { // JSON シリアライゼーション
            json(Json {
                prettyPrint = true
                isLenient = true
                ignoreUnknownKeys = true
                encodeDefaults = false
            })
        }
        install(HttpTimeout) { // タイムアウト設定
            requestTimeoutMillis = 15000
            connectTimeoutMillis = 15000
            socketTimeoutMillis = 15000
        }
        install(Logging) { // ログ出力
            level = LogLevel.INFO
        }
        install(DefaultRequest) { // デフォルトリクエスト設定
            header(HttpHeaders.ContentType, ContentType.Application.Json)
        }
    }
}

実装のポイント:

  • ignoreUnknownKeys = trueでAPIの変更で不要な設定値は無視できるので、今後バージョンアップで不要になった設定もエラーにならない
  • プラグイン方式なので必要な機能だけを追加
  • 設定が直感的で分かりやすい

エンジンとしてAndroidを指定していますが、これはAndroid専用の最適化されたエンジンです。他にCIO(Coroutine I/O)やOkHttpなども選択可能で、プロジェクトの要件に応じて変更できます。

GETリクエスト

// 基本的なGETリクエスト
suspend fun fetchUserData(userId: String): String {
    val response: HttpResponse = client.get("https://api.example.com/users/$userId")
    return response.bodyAsText()
}

// クエリパラメータ付きGET
suspend fun searchUsers(query: String, page: Int): String {
    val response = client.get("https://api.example.com/users") {
        parameter("q", query)
        parameter("page", page)
        parameter("limit", 20)
    }
    return response.bodyAsText()
}

POSTリクエスト

  • setBody(request)で自動的にJSON変換
  • フォームデータもサポート済み
  • 戻り値も自動的に型変換される
@Serializable
data class CreateUserRequest(
    val name: String,
    val email: String,
    val age: Int
)

@Serializable
data class User(
    val id: Long,
    val name: String,
    val email: String,
    val age: Int
)

// JSONでPOST
suspend fun createUser(request: CreateUserRequest): User {
    return client.post("https://api.example.com/users") {
        contentType(ContentType.Application.Json)
        setBody(request)
    }.body()
}

// フォームデータでPOST
suspend fun submitForm(name: String, email: String): String {
    val response = client.submitForm(
        url = "https://api.example.com/contact",
        formParameters = parameters {
            append("name", name)
            append("email", email)
        }
    )
    return response.bodyAsText()
}

PUTリクエスト・DELETEリクエスト

PUT、DELETEも全く同じパターンで書けます。

@Serializable
data class UpdateUserRequest(
    val name: String?,
    val email: String?,
    val age: Int?
)

suspend fun updateUser(userId: Long, request: UpdateUserRequest): User {
    return client.put("https://api.example.com/users/$userId") {
        contentType(ContentType.Application.Json)
        setBody(request)
    }.body()
}

// DELETEリクエスト
suspend fun deleteUser(userId: Long): Boolean {
    val response = client.delete("https://api.example.com/users/$userId")
    return response.status.isSuccess()
}

ヘッダーの設定

実際のアプリでは認証トークンやカスタムヘッダーが必要ですよね。Ktorならこれも簡単です!

  • 型安全なHttpHeaders定数も利用可能
  • 単一ヘッダーはheader()
  • 複数ヘッダーはheaders {}ブロック
// Bearerトークン
suspend fun fetchProtectedData(token: String): String {
    val response = client.get("https://api.example.com/protected") {
        header(HttpHeaders.Authorization, "Bearer $token")
    }
    return response.bodyAsText()
}

// API Key
suspend fun fetchDataWithApiKey(apiKey: String): String {
    val response = client.get("https://api.example.com/data") {
        header("X-API-Key", apiKey)
    }
    return response.bodyAsText()
}

// カスタムヘッダー
suspend fun fetchWithCustomHeaders(): String {
    val response = client.get("https://api.example.com/data") {
        headers {
            append("X-Custom-Header", "custom-value")
            append("User-Agent", "MyApp/1.0")
        }
    }
    return response.bodyAsText()
}

Retrofitだとヘッダー用のインターセプターを書く必要がありましたが、Ktorなら直接書けて楽チン!🎉

ファイルアップロード

import io.ktor.client.request.forms.*
import io.ktor.http.*
import java.io.File

suspend fun uploadFile(file: File): String {
    val response = client.submitFormWithBinaryData(
        url = "https://api.example.com/upload",
        formData = formData {
            append("file", file.readBytes(), Headers.build {
                append(HttpHeaders.ContentType, "image/jpeg")
                append(HttpHeaders.ContentDisposition, "filename=\"${file.name}\"")
            })
        }
    )
    return response.bodyAsText()
}

// マルチパートフォーム
suspend fun uploadWithMetadata(file: File, description: String): String {
    val response = client.submitFormWithBinaryData(
        url = "https://api.example.com/upload",
        formData = formData {
            append("description", description)
            append("file", file.readBytes(), Headers.build {
                append(HttpHeaders.ContentType, "image/jpeg")
                append(HttpHeaders.ContentDisposition, "filename=\"${file.name}\"")
            })
        }
    )
    return response.bodyAsText()
}

例では、ファイルはfile.readBytes()でByteArrayとして読み込んでいますが、大容量ファイルの場合はメモリ効率を考慮してストリーミングアップロードが推奨になります。

エラーハンドリング

基本的なエラーハンドリング

HTTPクライアントで一番重要なのがエラーハンドリング。Ktorは例外ベースで分かりやすいです。

suspend fun fetchDataWithErrorHandling(userId: String): Result<User> {
    return try {
        val user: User = client.get("https://api.example.com/users/$userId").body()
        Result.success(user)
    } catch (e: ClientRequestException) {
        // 4xx エラー
        when (e.response.status) {
            HttpStatusCode.NotFound -> Result.failure(Exception("ユーザーが見つかりません"))
            HttpStatusCode.Unauthorized -> Result.failure(Exception("認証が必要です"))
            else -> Result.failure(Exception("クライアントエラー: ${e.response.status}"))
        }
    } catch (e: ServerResponseException) {
        // 5xx エラー
        Result.failure(Exception("サーバーエラー: ${e.response.status}"))
    } catch (e: Exception) {
        Result.failure(e)
    }
}

Ktorの例外の種類

  • ClientRequestException: 4xxエラー(クライアント側の問題)
  • ServerResponseException: 5xxエラー(サーバー側の問題)
  • RedirectResponseException: 3xxリダイレクト
  • ResponseException: すべてのHTTPエラーの基底クラス

Retrofitと違って、HTTPステータスコードによって例外の種類が分かれているのが便利!4xx、5xxで処理を分けやすいです🎯

また、ネットワーク接続エラーやタイムアウトなどの通信エラーは通常のExceptionとしてキャッチされます。これにより、HTTPレスポンスエラーとネットワークレベルのエラーを明確に区別できます。

まとめ

2025年の現在、KtorはバージョンV3.2.3となり、安定性とパフォーマンスが大幅に向上しています。Kotlin Multiplatformの普及に伴い、今後さらに重要になるライブラリの一つです。

まずは小さなプロジェクトから始めて、徐々にKtorの強力な機能を活用していきましょう!


参考資料

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?