ユーザー情報をリクエストして、受け取った情報でカードViewを作る

Contents 非表示
Dependencies系セットアップ
AndroidManifest.xml
以下を追加
<uses-permission android:name="android.permission.INTERNET" />
bundle.gradle.kts (Module)
plugins
に以下を追加
id("kotlin-kapt")
id("org.jetbrains.kotlin.plugin.serialization") version "1.9.10"
dependencies
に以下を追加、syncする
implementation(libs.retrofit2.kotlinx.serialization.converter)
implementation(libs.kotlinx.serialization.json)
implementation(libs.converter.moshi)
implementation(libs.moshi.kotlin)
implementation("io.coil-kt:coil-compose:2.5.0")
kapt("com.squareup.moshi:moshi-kotlin-codegen:1.15.0")
implementation(libs.converter.gson)
implementation(libs.kotlinx.serialization.json)
implementation(libs.retrofit)
implementation(libs.retrofit2.kotlinx.serialization.converter.v080)
implementation(libs.okhttp)
実装
今回はこちらからユーザ情報を取得します。
Model (User.kt)
@Serializable
data class User(
val results: List<ResultsItem>
)
@Serializable
data class ResultsItem(
val phone: String,
val name: Name,
val location: Location,
val email: String,
val picture: Picture
)
@Serializable
data class Picture(
val thumbnail: String,
val large: String,
val medium: String
)
@Serializable
data class Name(
val last: String,
val title: String,
val first: String
)
@Serializable
data class Location(
val country: String,
val city: String,
)
// Preview用のダミーデータ、なくてもOK
val dummyUser = ResultsItem(
phone = "+123456789",
name = Name(title = "Mr", first = "John", last = "Doe"),
location = Location(
country = "USA",
city = "New York"
),
email = "john.doe@example.com",
picture = Picture(
thumbnail = "https://randomuser.me/api/portraits/thumb/men/1.jpg",
large = "https://randomuser.me/api/portraits/men/1.jpg",
medium = "https://randomuser.me/api/portraits/med/men/1.jpg"
)
)
API Request基盤
interface ApiService {
@GET("api/") // APIのエンドポイント(パス)を指定
suspend fun getUsers(): User // 非同期でユーザー情報を取得する
}
object RetrofitClient {
private const val BASE_URL = "https://randomuser.me/"
private val json = Json {
// JSONレスポンスに定義されていないキーが含まれていても無視
ignoreUnknownKeys = true
}
private val contentType = "application/json".toMediaType()
val apiService: ApiService by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(json.asConverterFactory(contentType)) // Kotlin Serializationを使用してJSONを変換
.build()
.create(ApiService::class.java)
}
}
ViewModel (UserViewModel.kt)
class UserViewModel : ViewModel() {
var users = mutableStateOf<List<ResultsItem>>(emptyList())
var isLoading = mutableStateOf(false)
var errorMessage = mutableStateOf<String?>(null)
init {
fetchUsers()
}
fun fetchUsers() {
isLoading.value = true
// ViewModelのライフサイクル内でコルーチンを開始
viewModelScope.launch {
try {
// RetrofitClientを使用してAPIからユーザーデータを取得
val response = RetrofitClient.apiService.getUsers()
users.value = response.results
isLoading.value = false
} catch (e: Exception) {
// エラーが発生した場合、エラーメッセージをMutableStateに設定
errorMessage.value = e.message
isLoading.value = false
}
}
}
}
View (MainActivity.kt)
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyJSONUserTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
val viewModel = UserViewModel()
UserListScreen(viewModel = viewModel)
}
}
}
}
}
@Composable
private fun UserListScreen(viewModel: UserViewModel) {
val users by viewModel.users
val isLoading by viewModel.isLoading
val errorMessage by viewModel.errorMessage
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Card(
modifier = Modifier
.fillMaxWidth(0.9f)
.height(480.dp)
.shadow(12.dp),
shape = RoundedCornerShape(16.dp)
) {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
if (isLoading) {
CircularProgressIndicator(modifier = Modifier.padding(16.dp))
} else if (errorMessage != null) {
ErrorView(viewModel, errorMessage)
} else {
UserItem(user = users[0], viewModel = viewModel)
}
}
}
}
}
@Composable
private fun ErrorView(
viewModel: UserViewModel,
errorMessage: String?
) {
Column(
modifier = Modifier.padding(16.dp)
) {
Button(
modifier = Modifier
.padding(top = 12.dp)
.align(Alignment.CenterHorizontally),
onClick = {
viewModel.fetchUsers()
}
) {
Text("Try Again")
}
Text(
"Error: ${errorMessage}",
modifier = Modifier.padding(16.dp)
)
}
}
@Composable
private fun UserItem(user: ResultsItem, viewModel: UserViewModel) {
Column(
modifier = Modifier.padding(12.dp)
) {
AsyncImage(
model = user.picture.large,
contentDescription = "User Picture",
alignment = Alignment.Center,
contentScale = ContentScale.Crop,
modifier = Modifier
.padding(8.dp)
.size(200.dp)
.clip(CircleShape)
.align(Alignment.CenterHorizontally)
)
Text("Name: ${user.name.first} ${user.name.last}")
Text("Email: ${user.email}")
Text("Phone: ${user.phone}")
Text("Location: ${user.location.city}, ${user.location.country}")
Button(
modifier = Modifier
.padding(top = 12.dp)
.align(Alignment.CenterHorizontally),
onClick = {
viewModel.fetchUsers()
}
) {
Text("Fetch Another User Info")
}
}
}
@Preview(showBackground = true)
@Composable
fun UserItemPreview() {
val dummyUser = dummyUser
val viewModel = UserViewModel()
MyJSONUserTheme {
UserItem(user = dummyUser, viewModel = viewModel)
}
}