Retrofit Complete Guide — API Communication in Compose Apps
Retrofit is the standard HTTP client library for Android. This guide covers modern Kotlin patterns with sealed result types and coroutines.
1. Retrofit Setup with Serialization
import com.squareup.retrofit2.Retrofit
import kotlinx.serialization.json.Json
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import okhttp3.MediaType.Companion.toMediaType
val json = Json { ignoreUnknownKeys = true }
val retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
.build()
2. API Interface with Annotations
import retrofit2.http.*
import kotlinx.serialization.Serializable
@Serializable
data class User(val id: Int, val name: String, val email: String)
@Serializable
data class CreateUserRequest(val name: String, val email: String)
interface UserApi {
@GET("users/{id}")
suspend fun getUser(@Path("id") userId: Int): User
@GET("users")
suspend fun listUsers(@Query("page") page: Int = 1): List<User>
@POST("users")
suspend fun createUser(@Body request: CreateUserRequest): User
@PUT("users/{id}")
suspend fun updateUser(@Path("id") userId: Int, @Body request: CreateUserRequest): User
@DELETE("users/{id}")
suspend fun deleteUser(@Path("id") userId: Int)
}
3. OkHttpClient with Logging & Authentication
import okhttp3.OkHttpClient
import okhttp3.Interceptor
import okhttp3.logging.HttpLoggingInterceptor
import java.util.concurrent.TimeUnit
val httpClient = OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
.addInterceptor { chain ->
val original = chain.request()
val request = original.newBuilder()
.header("Authorization", "Bearer your-token-here")
.header("Accept", "application/json")
.build()
chain.proceed(request)
}
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.build()
val retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/")
.client(httpClient)
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
.build()
4. Repository Pattern with Result Type
sealed class Result<out T> {
data class Success<T>(val data: T) : Result<T>()
data class Error(val exception: Exception, val message: String) : Result<Nothing>()
object Loading : Result<Nothing>()
}
class UserRepository(private val userApi: UserApi) {
suspend fun getUser(userId: Int): Result<User> = try {
val user = userApi.getUser(userId)
Result.Success(user)
} catch (e: HttpException) {
Result.Error(e, "HTTP ${e.code()}: ${e.message()}")
} catch (e: IOException) {
Result.Error(e, "Network error: ${e.message}")
}
suspend fun createUser(name: String, email: String): Result<User> = try {
val request = CreateUserRequest(name, email)
val user = userApi.createUser(request)
Result.Success(user)
} catch (e: Exception) {
Result.Error(e, e.message ?: "Unknown error")
}
}
5. ViewModel with Sealed State
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
sealed class UserUiState {
object Loading : UserUiState()
data class Success(val user: User) : UserUiState()
data class Error(val message: String) : UserUiState()
}
class UserViewModel(private val repository: UserRepository) : ViewModel() {
private val _uiState = MutableStateFlow<UserUiState>(UserUiState.Loading)
val uiState: StateFlow<UserUiState> = _uiState
fun loadUser(userId: Int) {
viewModelScope.launch {
_uiState.value = UserUiState.Loading
when (val result = repository.getUser(userId)) {
is Result.Success -> _uiState.value = UserUiState.Success(result.data)
is Result.Error -> _uiState.value = UserUiState.Error(result.message)
is Result.Loading -> {}
}
}
}
}
6. Compose UI with Loading/Error States
import androidx.compose.material3.*
import androidx.compose.runtime.collectAsState
import androidx.compose.foundation.layout.*
@Composable
fun UserScreen(viewModel: UserViewModel) {
val uiState = viewModel.uiState.collectAsState()
Box(modifier = Modifier.fillMaxSize()) {
when (val state = uiState.value) {
is UserUiState.Loading -> {
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
}
is UserUiState.Success -> {
Column(modifier = Modifier.padding(16.dp)) {
Text("${state.user.name} (ID: ${state.user.id})", style = MaterialTheme.typography.headlineSmall)
Text(state.user.email)
}
}
is UserUiState.Error -> {
Column(modifier = Modifier.align(Alignment.Center), horizontalAlignment = Alignment.CenterHorizontally) {
Text("Error: ${state.message}", color = MaterialTheme.colorScheme.error)
Button(onClick = { viewModel.loadUser(1) }) {
Text("Retry")
}
}
}
}
}
}
This pattern (Retrofit → Repository → ViewModel → Compose) is the modern Android standard for robust API communication.
8 Android app templates: Gumroad
Top comments (0)