🛸
【Android】Ktor+HiltでAPIクライアント実装
概要
前回の続きになります。今回はさらにHiltを使ってDIできるようにしたり、その他細かい部分の実装を進めていきます。
環境構築や準備
各バージョンや環境
# Android Studio Jellyfish | 2023.3.1
$ sw_vers
ProductName: macOS
ProductVersion: 14.5
BuildVersion: 23F79
プロジェクトは前回のものを継続して使う想定で進めていきます。
また、今回は👇の無料で使えるモック用のAPIを使います。
1. Hiltのインストール
gradle/libs.versions.toml
に以下を追加
1-1. [versions]
# ※ kotlinのversionは "2.0.0"
hilt = "2.51.1"
ksp = "2.0.0-1.0.22"
[libraries]
# ktor
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
ktor-client-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
# hilt
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" }
hilt-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hilt" }
[plugins]
# kotlin 2.0.0 対応
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
jetbrains-kotlin-plugin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
ksp-gradle-plugin = { id = "com.google.devtools.ksp", version.ref = "ksp" }
hilt-android-gradle-plugin = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
今回は kotlin.serialization も使うため追加してます。
また io.ktor:ktor-client-content-negotiation
と io.ktor:ktor-serialization-kotlinx-json
に関してはクライアントとサーバー間のメディア タイプのネゴシエーションと、JSON 形式での応答のシリアル化/逆シリアル化のために使用されます。
build.gradle.kts
に以下を追加
1-2. plugins {
alias(libs.plugins.jetbrains.kotlin.plugin.serialization) apply false
alias(libs.plugins.ksp.gradle.plugin) apply false
alias(libs.plugins.hilt.android.gradle.plugin) apply false
alias(libs.plugins.compose.compiler) apply false
}
app/build.gradle.kts
に以下を追加
1-3. plugins {
alias(libs.plugins.jetbrains.kotlin.plugin.serialization)
alias(libs.plugins.ksp.gradle.plugin)
alias(libs.plugins.hilt.android.gradle.plugin)
alias(libs.plugins.compose.compiler)
}
dependencies {
// ktor
implementation(libs.ktor.client.content.negotiation)
implementation(libs.ktor.client.serialization.kotlinx.json)
// hilt
implementation(libs.hilt.android)
ksp(libs.hilt.compiler)
}
2. Hiltのセットアップ
1-1. Hiltアプリケーションクラスの作成
MainApplication.kt
を以下内容で作成します。
@HiltAndroidApp
class MainApplication : Application() {
override fun onCreate() {
super.onCreate()
}
}
AndroidManifest.xml
に以下を追加します。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<application
....
android:name=".MainApplication" ← 追加
>
1-2. @AndroidEntryPoint の設定
MainActivity
に @AndroidEntryPoint
を設定します。
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
// ...
1-3. Hiltモジュールの作成
以下内容で ApiModule
を作成します。
@Module
@InstallIn(SingletonComponent::class)
object ApiModule {
@Singleton
@Provides
fun provideHttpClient(): HttpClient {
return HttpClient(OkHttp){
install(DefaultRequest){
url("https://jsonplaceholder.typicode.com")
header(HttpHeaders.ContentType, ContentType.Application.Json)
}
install(ContentNegotiation){
json(Json)
}
engine {
addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BASIC
})
}
}
}
}
3. Serviceクラス実装
3-1. Resultクラス定義
API用のResultクラスを以下内容で作成しときます。
sealed class ApiResult<T>(val data: T? = null, val error: String? = null){
class Success<T>(todo: T):ApiResult<T>(data = todo)
class Error<T>(error: String):ApiResult<T>(error = error)
class Loading<T>:ApiResult<T>()
}
3-2. モデル実装
以下内容で Todo.kt
を作成します。
package com.example.ktorexample.data
@kotlinx.serialization.Serializable
data class Todo(
val id: Int,
val userId: Int,
val title: String,
val completed: Boolean
)
3-2. TodoServiceクラス実装
実際にリクエストする処理の TodoService.kt
を以下内容で作成します。
class TodoService @Inject constructor(private val client: HttpClient) {
suspend fun todo(id: Int): Flow<ApiResult<Todo>> = flow {
emit(ApiResult.Loading())
try {
val response = client.get("/todos/$id")
emit(ApiResult.Success(response.body()))
} catch (e: Exception) {
emit(ApiResult.Error(e.message ?: "Something went wrong"))
}
}
}
4. Compose部分に反映
最後に MainActivity.kt
を以下内容に修正します。
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
@Inject
lateinit var todoService: TodoService
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
KtorExampleTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
val scope = rememberCoroutineScope()
var todo by remember { mutableStateOf<Todo?>(null) }
LaunchedEffect(true) {
scope.launch {
try {
todoService.todo(1).collect { todo = it.data }
} catch (e: Exception) {
e.localizedMessage ?: "error"
}
}
}
Todo(
todo = todo,
modifier = Modifier.padding(innerPadding)
)
}
}
}
}
}
@Composable
fun Todo(todo: Todo?, modifier: Modifier = Modifier) {
Text(
text = "Todo: ${todo?.title}",
modifier = modifier
)
}
実際に起動してみると👇のようにレスポンスの title
部分が反映されていればOKです。
参考URL
Discussion