Retrofit trong Android
1. Giới thiệu
Retrofit là type-safe HTTP client cho Android và Java, giúp dễ dàng consume REST APIs.
2. Setup
// build.gradle.kts
dependencies {
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
}3. Data Models
data class User(
val id: Int,
val name: String,
val email: String,
@SerializedName("avatar_url")
val avatarUrl: String
)
data class CreateUserRequest(
val name: String,
val email: String
)
data class ApiResponse<T>(
val data: T,
val message: String,
val success: Boolean
)4. API Interface
interface ApiService {
@GET("users")
suspend fun getUsers(): List<User>
@GET("users/{id}")
suspend fun getUser(@Path("id") userId: Int): User
@GET("users")
suspend fun searchUsers(
@Query("q") query: String,
@Query("page") page: Int = 1,
@Query("limit") limit: Int = 20
): List<User>
@POST("users")
suspend fun createUser(@Body request: CreateUserRequest): User
@PUT("users/{id}")
suspend fun updateUser(
@Path("id") userId: Int,
@Body user: User
): User
@DELETE("users/{id}")
suspend fun deleteUser(@Path("id") userId: Int)
@Multipart
@POST("users/{id}/avatar")
suspend fun uploadAvatar(
@Path("id") userId: Int,
@Part image: MultipartBody.Part
): User
@Headers("Cache-Control: max-age=300")
@GET("config")
suspend fun getConfig(): Config
}5. Retrofit Instance
object RetrofitClient {
private const val BASE_URL = "https://api.example.com/"
private val loggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
private val okHttpClient = OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.addInterceptor { chain ->
val request = chain.request().newBuilder()
.addHeader("Authorization", "Bearer ${getToken()}")
.addHeader("Accept", "application/json")
.build()
chain.proceed(request)
}
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
private val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
val apiService: ApiService = retrofit.create(ApiService::class.java)
}6. Repository Pattern
class UserRepository(private val api: ApiService) {
suspend fun getUsers(): Result<List<User>> {
return try {
val users = api.getUsers()
Result.success(users)
} catch (e: Exception) {
Result.failure(e)
}
}
suspend fun getUser(id: Int): Result<User> {
return try {
val user = api.getUser(id)
Result.success(user)
} catch (e: Exception) {
Result.failure(e)
}
}
suspend fun createUser(name: String, email: String): Result<User> {
return try {
val user = api.createUser(CreateUserRequest(name, email))
Result.success(user)
} catch (e: Exception) {
Result.failure(e)
}
}
}7. ViewModel Integration
class UserViewModel(private val repository: UserRepository) : ViewModel() {
private val _users = MutableStateFlow<UiState<List<User>>>(UiState.Loading)
val users: StateFlow<UiState<List<User>>> = _users.asStateFlow()
init {
loadUsers()
}
fun loadUsers() {
viewModelScope.launch {
_users.value = UiState.Loading
repository.getUsers()
.onSuccess { _users.value = UiState.Success(it) }
.onFailure { _users.value = UiState.Error(it.message ?: "Error") }
}
}
}
sealed class UiState<out T> {
object Loading : UiState<Nothing>()
data class Success<T>(val data: T) : UiState<T>()
data class Error(val message: String) : UiState<Nothing>()
}8. Error Handling
suspend fun <T> safeApiCall(apiCall: suspend () -> T): Result<T> {
return try {
Result.success(apiCall())
} catch (e: HttpException) {
val errorBody = e.response()?.errorBody()?.string()
Result.failure(ApiException(e.code(), errorBody ?: "HTTP Error"))
} catch (e: IOException) {
Result.failure(NetworkException("Network error"))
} catch (e: Exception) {
Result.failure(e)
}
}
class ApiException(val code: Int, message: String) : Exception(message)
class NetworkException(message: String) : Exception(message)9. Upload Files
suspend fun uploadImage(file: File): Result<String> {
val requestFile = file.asRequestBody("image/*".toMediaTypeOrNull())
val body = MultipartBody.Part.createFormData("image", file.name, requestFile)
return safeApiCall {
api.uploadAvatar(userId, body)
}
}10. Compose Integration
@Composable
fun UserListScreen(viewModel: UserViewModel = viewModel()) {
val uiState by viewModel.users.collectAsState()
when (val state = uiState) {
is UiState.Loading -> {
CircularProgressIndicator()
}
is UiState.Success -> {
LazyColumn {
items(state.data) { user ->
UserItem(user)
}
}
}
is UiState.Error -> {
Column {
Text("Error: ${state.message}")
Button(onClick = { viewModel.loadUsers() }) {
Text("Retry")
}
}
}
}
}📝 Tóm tắt
| Annotation | Mô tả |
|---|---|
@GET | HTTP GET request |
@POST | HTTP POST request |
@PUT | HTTP PUT request |
@DELETE | HTTP DELETE request |
@Path | URL path parameter |
@Query | URL query parameter |
@Body | Request body |
@Header | Request header |
@Multipart | Multipart request |
Last updated on