Networking with Retrofit

Reviewed & published by Brayan K

Almost every app talks to a server. Retrofit turns a simple annotated Kotlin interface into a fully working, type-safe HTTP client — and with coroutines, network calls read like ordinary code.

Part of the free Kotlin course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

By the end you'll define an API interface with @GET/@POST and suspend functions, plug in a converter, and meet the Ktor client alternative.

What You'll Learn in This Lesson

1️⃣ Describing the API

You declare endpoints on an interface using annotations. @GET sets the method and path; @Path fills URL placeholders; @Query adds query parameters. Mark calls suspend so they run in a coroutine.

import kotlinx.serialization.Serializable
import retrofit2.http.GET
import retrofit2.http.Path
import retrofit2.http.Query

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

// Describe the API as an interface with annotated methods.
interface UserApi {

    // GET https://.../users?page=1
    @GET("users")
    suspend fun listUsers(@Query("page") page: Int): List<User>

    // GET https://.../users/{id}
    @GET("users/{id}")
    suspend fun getUser(@Path("id") id: Int): User
}

The interface is pure description. Retrofit generates the working client from it at runtime.

2️⃣ Building Retrofit and Making a Call

A Retrofit.Builder sets the base URL and a converter (here a kotlinx.serialization Json converter), then create() returns a live implementation.

import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import retrofit2.Retrofit
import retrofit2.converter.kotlinx.serialization.asConverterFactory

suspend fun fetchUser() {
    val json = Json { ignoreUnknownKeys = true }

    val retrofit = Retrofit.Builder()
        .baseUrl("https://example.com/api/")
        // A converter turns HTTP bodies into Kotlin objects.
        .addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
        .build()

    // create() returns a working implementation of UserApi.
    val api = retrofit.create(UserApi::class.java)

    // suspend call -> runs off the main thread inside a coroutine.
    val user = api.getUser(1)
    println(user.name)
}

3️⃣ The Ktor Client Alternative

Ktor's HttpClient is a Kotlin-first, multiplatform alternative. It uses suspend functions and a content-negotiation plugin for JSON.

import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.request.get
import io.ktor.client.engine.cio.CIO
import io.ktor.serialization.kotlinx.json.json
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import kotlinx.serialization.Serializable

@Serializable
data class Post(val id: Int, val title: String)

// Ktor client is a multiplatform alternative to Retrofit.
suspend fun fetchPost(): Post {
    val client = HttpClient(CIO) {
        install(ContentNegotiation) { json() }
    }
    // A suspend call that returns the deserialized body.
    return client.get("https://example.com/posts/1").body()
}

Your turn. Replace the TODO, then reason about the URL.

// 🎯 YOUR TURN — replace the TODO then reason about the URL.

import retrofit2.http.GET
import retrofit2.http.Path

interface RepoApi {

    // 1) Map this method to GET repos/{owner}/{name}
    //    👉 replace TODO with: @GET("repos/{owner}/{name}")
    TODO
    suspend fun getRepo(
        @Path("owner") owner: String,
        @Path("name") name: String
    ): String
}

Mini-Challenge: A Search Endpoint

Add a search method with a query parameter and a list return type.

// 🎯 MINI-CHALLENGE: a search endpoint.
//
// 1. In an interface SearchApi, add a suspend method search().
// 2. Map it to @GET("search").
// 3. Add a query parameter: @Query("q") term: String.
// 4. Return List<Result> (assume @Serializable data class Result).
//
// ✅ search("kotlin") should issue: GET search?q=kotlin

interface SearchApi {
    // your code here
}

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Retrofit

ConceptKotlin
GET endpoint@GET("users") suspend fun list(): List<User>
Path param@Path("id") id: Int
Query param@Query("page") page: Int
BuildRetrofit.Builder().baseUrl(...).build()
Implementretrofit.create(Api::class.java)

🎉 Lesson Complete!

Practice quiz

In Retrofit, you describe an API as what?

  • A data class
  • A Kotlin interface with annotated methods
  • An XML file
  • An abstract class with bodies

Answer: A Kotlin interface with annotated methods. Retrofit turns an annotated interface into a working HTTP client implementation.

Which annotation maps a method to an HTTP GET request?

  • @Get
  • @HttpGet
  • @GET
  • @Fetch

Answer: @GET. @GET("path") marks a method as an HTTP GET to that path.

Which annotation is used for sending data to create a resource?

  • @POST
  • @SEND
  • @PUSH
  • @PUT only

Answer: @POST. @POST("path") issues an HTTP POST, typically with a @Body payload.

Why mark a Retrofit method as 'suspend'?

  • To make it synchronous
  • So it can be called from a coroutine and not block the main thread
  • To cache results
  • To make it private

Answer: So it can be called from a coroutine and not block the main thread. A suspend function lets Retrofit run the call asynchronously from a coroutine.

What does a converter (e.g. a Json converter) do in Retrofit?

  • Encrypts traffic
  • Converts between HTTP bodies and your Kotlin objects
  • Manages the back stack
  • Schedules background jobs

Answer: Converts between HTTP bodies and your Kotlin objects. A converter serializes request bodies and deserializes responses into your data classes.

How do you supply a path parameter like /users/{id} in Retrofit?

  • With @Query
  • With @Header
  • With @Field
  • With @Path("id")

Answer: With @Path("id"). @Path("id") binds a method parameter into the {id} placeholder in the URL.

How do you add a query parameter such as ?page=2?

  • @Query("page")
  • @Path("page")
  • @Body
  • @Url

Answer: @Query("page"). @Query("page") appends a query string parameter to the request URL.

What does Retrofit.Builder().baseUrl(...).build().create(Api::class.java) return?

  • A raw JSON string
  • A generated implementation of your Api interface
  • A coroutine scope
  • A database

Answer: A generated implementation of your Api interface. create() returns a working implementation of the annotated interface.

What is Ktor client in this context?

  • A UI toolkit
  • A multiplatform HTTP client, an alternative to Retrofit
  • A serialization format
  • An Android Activity

Answer: A multiplatform HTTP client, an alternative to Retrofit. Ktor's HttpClient is a Kotlin-first, multiplatform alternative to Retrofit for networking.

Where should you typically launch a network call in an Android app?

  • On the main thread directly
  • In onCreate synchronously
  • In a coroutine, e.g. within viewModelScope
  • In the manifest

Answer: In a coroutine, e.g. within viewModelScope. Network calls run in coroutines (often viewModelScope) so they don't block the UI thread.

Continue this course

Frequently asked questions

Why is a Retrofit API an interface, not a class?

You only declare what the endpoints look like — the path, HTTP method, parameters, and return type — using annotations. Retrofit generates the actual implementation at runtime from that interface via retrofit.create(Api::class.java). This keeps your networking code declarative: you describe the API surface and let the library handle building requests, executing them, and parsing responses.

What do @GET, @POST, @Path, and @Query do?

@GET and @POST set the HTTP method and the endpoint path. @Path("id") substitutes a method argument into a {id} placeholder in the URL, so getUser(5) hits users/5. @Query("page") appends a query string such as ?page=2. For POST bodies you typically add a @Body parameter holding a serializable object. Together they map a Kotlin method to a precise HTTP request.

Why mark the methods as suspend?

Network calls must not block the main thread. Declaring the method suspend lets Retrofit run it asynchronously and resume your coroutine with the result, so the call reads like sequential code while staying off the UI thread. You launch these calls from a coroutine scope — commonly viewModelScope in an Android ViewModel.

How does Retrofit turn JSON into my objects, and what's Ktor?

A converter factory does the conversion. Pairing Retrofit with a kotlinx.serialization Json converter means responses are deserialized straight into your @Serializable data classes and request bodies are serialized for you. Ktor's HttpClient is an alternative, Kotlin-first networking library that works across platforms (Android, iOS, JVM); it uses content-negotiation plugins to do the same JSON conversion.