Kotlin REST API and Retrofit

Most Android apps fetch data from the internet — a news feed, weather info, user profiles. Retrofit is the most popular library for making HTTP network requests in Android. It turns your API endpoints into simple Kotlin function calls.

What is a REST API

A REST API is a server that responds to HTTP requests with data, usually in JSON format. Your Android app sends a request to a URL, and the server sends back data your app can display.

Diagram — Client–Server Communication

Android App (Client)               Server (REST API)
────────────────────               ─────────────────
GET https://api.example.com/users
        │
        ▼ HTTP Request
────────────────────────────────►
                                  Server processes request
◄────────────────────────────────
        │ HTTP Response (JSON)
        ▼
[{"id":1,"name":"Priya"},
 {"id":2,"name":"Arun"}]
        │
        ▼
App parses JSON → displays list

Adding Dependencies

// build.gradle.kts (app level)
dependencies {
    // Retrofit — HTTP client
    implementation("com.squareup.retrofit2:retrofit:2.9.0")

    // Gson converter — converts JSON to Kotlin data classes
    implementation("com.squareup.retrofit2:converter-gson:2.9.0")

    // Coroutines support
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")

    // ViewModel + LiveData
    implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
}

Also add internet permission to AndroidManifest.xml:

<uses-permission android:name="android.permission.INTERNET" />

Step 1 — Define the Data Model

Create a Kotlin data class that matches the JSON structure the API returns.

// JSON from API:
// { "id": 1, "name": "Leanne Graham", "email": "sincere@april.biz" }

data class User(
    val id: Int,
    val name: String,
    val email: String
)

Step 2 — Create the API Interface

Define an interface where each function represents one API endpoint. Retrofit reads the annotations and builds the actual HTTP calls for you.

import retrofit2.http.*

interface UserApiService {

    // GET all users
    @GET("users")
    suspend fun getUsers(): List<User>

    // GET a single user by ID
    @GET("users/{id}")
    suspend fun getUserById(@Path("id") userId: Int): User

    // POST — create a new user
    @POST("users")
    suspend fun createUser(@Body user: User): User

    // PUT — update an existing user
    @PUT("users/{id}")
    suspend fun updateUser(@Path("id") id: Int, @Body user: User): User

    // DELETE
    @DELETE("users/{id}")
    suspend fun deleteUser(@Path("id") id: Int)

    // GET with query parameters: /users?page=2&limit=10
    @GET("users")
    suspend fun getUsersPaged(
        @Query("page") page: Int,
        @Query("limit") limit: Int
    ): List<User>
}

Step 3 — Build the Retrofit Instance

Create one Retrofit instance for your entire app. Use a singleton object to avoid creating it multiple times.

import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

object RetrofitClient {
    private const val BASE_URL = "https://jsonplaceholder.typicode.com/"

    val apiService: UserApiService by lazy {
        Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(UserApiService::class.java)
    }
}

Diagram — Retrofit Architecture

Your Code                  Retrofit               Server
──────────                 ────────               ──────
apiService.getUsers()
    │
    ▼
Retrofit reads @GET("users")
    │
    ▼
Builds HTTP request:
GET https://jsonplaceholder.typicode.com/users
    │
    ────────────────────────────────────────►
                                            Sends JSON response
    ◄────────────────────────────────────────
    │
    ▼
GsonConverterFactory parses JSON
    │
    ▼
Returns List<User> to your code

Step 4 — Create the Repository

class UserRepository {
    private val api = RetrofitClient.apiService

    suspend fun fetchUsers(): Result<List<User>> {
        return try {
            val users = api.getUsers()
            Result.success(users)
        } catch (e: Exception) {
            Result.failure(e)
        }
    }
}

Step 5 — Create the ViewModel

import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch

class UserViewModel : ViewModel() {
    private val repository = UserRepository()

    val users = MutableLiveData<List<User>>()
    val error = MutableLiveData<String>()
    val isLoading = MutableLiveData<Boolean>()

    fun loadUsers() {
        viewModelScope.launch {
            isLoading.value = true
            val result = repository.fetchUsers()
            result
                .onSuccess  { users.value = it }
                .onFailure  { error.value = it.message }
            isLoading.value = false
        }
    }
}

Step 6 — Display in Activity

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    private val viewModel: UserViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        viewModel.isLoading.observe(this) { loading ->
            binding.progressBar.visibility = if (loading) View.VISIBLE else View.GONE
        }

        viewModel.users.observe(this) { userList ->
            // Update RecyclerView adapter with userList
            binding.tvOutput.text = userList.joinToString("\n") { it.name }
        }

        viewModel.error.observe(this) { msg ->
            Toast.makeText(this, "Error: $msg", Toast.LENGTH_LONG).show()
        }

        viewModel.loadUsers()
    }
}

Sending Headers and Auth Tokens

// Static header
@Headers("Content-Type: application/json")
@GET("profile")
suspend fun getProfile(): User

// Dynamic header
@GET("profile")
suspend fun getProfile(@Header("Authorization") token: String): User

// Usage:
val profile = api.getProfile("Bearer eyJhbGciOiJIUzI1NiIsInR...")

Complete Request–Response Flow

User taps "Load"
       │
       ▼
Activity calls viewModel.loadUsers()
       │
       ▼
ViewModel launches coroutine on viewModelScope
       │
       ▼
Repository calls api.getUsers() on Dispatchers.IO
       │
       ▼
Retrofit sends GET /users to server
       │
       ▼
Server returns JSON array
       │
       ▼
Gson converts JSON → List<User>
       │
       ▼
Repository returns Result.success(users)
       │
       ▼
ViewModel posts to users LiveData
       │
       ▼
Activity observe block runs
       │
       ▼
RecyclerView displays list on screen

Testing With a Free Public API

JSONPlaceholder (jsonplaceholder.typicode.com) is a free fake REST API for testing and prototyping. It has ready-made endpoints for users, posts, comments, albums, and todos — no sign-up required. Use it to practice Retrofit without setting up a backend.

// Example endpoints:
// GET  https://jsonplaceholder.typicode.com/users
// GET  https://jsonplaceholder.typicode.com/users/1
// GET  https://jsonplaceholder.typicode.com/posts
// POST https://jsonplaceholder.typicode.com/posts

Leave a Comment

Your email address will not be published. Required fields are marked *