DEV Community

myougaTheAxo
myougaTheAxo

Posted on

Retrofit Complete Guide — API Communication in Compose Apps

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()
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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")
    }
}
Enter fullscreen mode Exit fullscreen mode

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 -> {}
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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")
                    }
                }
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

This pattern (Retrofit → Repository → ViewModel → Compose) is the modern Android standard for robust API communication.


8 Android app templates: Gumroad

Top comments (0)