惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Recent Announcements
Recent Announcements
J
Java Code Geeks
雷峰网
雷峰网
Microsoft Security Blog
Microsoft Security Blog
博客园 - 【当耐特】
腾讯CDC
博客园 - 司徒正美
B
Blog RSS Feed
博客园 - 三生石上(FineUI控件)
I
InfoQ
N
Netflix TechBlog - Medium
L
LangChain Blog
博客园_首页
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
T
Tailwind CSS Blog
MyScale Blog
MyScale Blog
美团技术团队
The Cloudflare Blog
爱范儿
爱范儿
Stack Overflow Blog
Stack Overflow Blog
博客园 - 聂微东
H
Help Net Security
Martin Fowler
Martin Fowler
V
Visual Studio Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
The Complete Retrofit Lifecycle in a Real Android App
Aalaa Fahiem · 2026-06-16 · via DEV Community

Most Retrofit tutorials show you isolated code snippets. This article traces the entire lifecycle of a Retrofit call — from app startup to data appearing on screen — using real code from a Pokedex app built with Kotlin, Hilt, Coroutines, and Jetpack Compose.

By the end, you'll understand every link in the chain

What Is Retrofit?

Retrofit is a type-safe HTTP client for Android and Java. Instead of writing raw HTTP requests, you describe your API as a Kotlin interface, and Retrofit generates the networking code for you behind the scenes.

interface PokeApi {
    @GET("pokemon")
    suspend fun getPokemonList(
        @Query("limit") limit: Int,
        @Query("offset") offset: Int,
    ): PokemonList

    @GET("pokemon/{name}")
    suspend fun getPokemonInfo(
        @Path("name") name: String,
    ): Pokemon
}

That's it. No manual HttpURLConnection, no manual JSON parsing. Retrofit handles building the request and Gson (or Moshi) handles converting the JSON response into your Kotlin objects.


The 3 Building Blocks

Every Retrofit setup needs exactly three things:

  1. The Interface — what requests can be made
  2. The Builder — where to send them and how to parse responses
  3. The Response model — what shape the data comes back in
val retrofit = Retrofit.Builder()
    .baseUrl("https://pokeapi.co/api/v2/")
    .addConverterFactory(GsonConverterFactory.create())
    .build()

val api = retrofit.create(PokeApi::class.java)


The Full Lifecycle, Step by Step

Here's the entire request lifecycle traced through a real app architecture: Compose UI → ViewModel → Repository → Retrofit → OkHttp → Server → Gson → back up the chain.

Step 1 — App Starts, Hilt Wakes Up

@HiltAndroidApp
class PokedexApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        Timber.plant(Timber.DebugTree())
    }
}

@HiltAndroidApp tells Hilt to start managing dependency injection across the app.

Step 2 — Hilt Builds Retrofit Once (Singleton)

@Module
@InstallIn(SingletonComponent::class)
object AppModule {

    @Singleton
    @Provides
    fun providePokeApi(): PokeApi {
        val okHttpClient = OkHttpClient.Builder()
            .connectTimeout(30, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .retryOnConnectionFailure(true)
            .addInterceptor { chain ->
                val request = chain.request().newBuilder()
                    .header("User-Agent", "Mozilla/5.0 ...")
                    .header("Accept", "application/json")
                    .build()
                chain.proceed(request)
            }
            .build()

        return Retrofit.Builder()
            .baseUrl(Constants.BASE_URL)
            .client(okHttpClient)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(PokeApi::class.java)
    }

    @Singleton
    @Provides
    fun providePokemonRepository(api: PokeApi) = PokemonRepository(api)
}

Building a Retrofit instance is expensive — connection pools, thread pools, parsers. @Singleton ensures it's built once and reused everywhere, instead of recreated per screen.

Step 3 — ViewModel Is Created, Requests Data Immediately

@HiltViewModel
class PokemonListViewModel @Inject constructor(
    private val repository: PokemonRepository,
) : ViewModel() {

    var pokemonList = mutableStateOf<List<PokedexListEntry>>(listOf())
    var isLoading = mutableStateOf(false)

    init {
        loadPokemonPaginated()
    }

    fun loadPokemonPaginated() {
        viewModelScope.launch {
            isLoading.value = true
            val result = repository.getPokemonList(PAGE_SIZE, curPage * PAGE_SIZE)
            // handle result below
        }
    }
}

Hilt injects the PokemonRepository automatically — no manual instantiation needed. viewModelScope.launch starts a coroutine so the network call doesn't block the main thread.

Step 4 — Repository Calls the API and Handles Failure

@Singleton
class PokemonRepository @Inject constructor(
    private val api: PokeApi,
) {
    suspend fun getPokemonList(limit: Int, offset: Int): Resource<PokemonList> {
        val response = try {
            api.getPokemonList(limit, offset)
        } catch (e: Exception) {
            return Resource.Error("Couldn't load Pokemon")
        }
        return Resource.Success(response)
    }
}

The Repository pattern decouples the ViewModel from the data source. The ViewModel doesn't know — or care — whether data comes from a network call, a cache, or a database.

Step 5 — Retrofit Builds the URL and OkHttp Sends It

Given:

@GET("pokemon")
suspend fun getPokemonList(
    @Query("limit") limit: Int,
    @Query("offset") offset: Int,
): PokemonList

Calling getPokemonList(20, 0) produces:

https://pokeapi.co/api/v2/pokemon?limit=20&offset=0

OkHttp (the engine underneath Retrofit) opens the connection, attaches any interceptor headers, and sends the request over the wire.

Step 6 — Server Responds With JSON

{
  "count": 1302,
  "next": "https://pokeapi.co/api/v2/pokemon?offset=20&limit=20",
  "previous": null,
  "results": [
    { "name": "bulbasaur", "url": "https://pokeapi.co/api/v2/pokemon/1/" }
  ]
}

Step 7 — Gson Converts JSON Into Kotlin Objects

data class PokemonList(
    @SerializedName("count") val count: Int,
    @SerializedName("next") val next: String,
    @SerializedName("previous") val previous: Any,
    @SerializedName("results") val results: List<Result>,
)

@SerializedName maps a JSON key to a Kotlin property — essential when the server uses snake_case and your code uses camelCase.

Step 8 — Result Flows Back Up, Wrapped in a Sealed Class

sealed class Resource<T>(val data: T? = null, val message: String? = null) {
    class Success<T>(data: T) : Resource<T>(data)
    class Error<T>(message: String, data: T? = null) : Resource<T>(data, message)
    class Loading<T>(data: T? = null) : Resource<T>(data)
}

This wrapper lets the ViewModel handle success, error, and loading as explicit states rather than nullable guesswork.

Step 9 — ViewModel Updates State, Compose Redraws

when (result) {
    is Resource.Success -> {
        pokemonList.value += result.data!!.results.map { /* map to UI model */ }
        isLoading.value = false
    }
    is Resource.Error -> {
        loadError.value = result.message!!
        isLoading.value = false
    }
    else -> {}
}

Because pokemonList is a Compose mutableStateOf, the moment it changes, any composable reading it recomposes automatically. No manual UI refresh needed.


The Full Chain, Visualized

App starts → Hilt builds Retrofit + Repository (once)
    ↓
ViewModel created → Repository injected → init{} fires
    ↓
viewModelScope.launch → repository.getPokemonList()
    ↓
api.getPokemonList() → Retrofit builds the URL
    ↓
OkHttp attaches headers, sends the HTTP request
    ↓
Server responds with JSON
    ↓
Gson converts JSON → Kotlin data class
    ↓
Repository wraps it in Resource.Success / Resource.Error
    ↓
ViewModel updates mutableStateOf
    ↓
Compose recomposes → user sees the data


What to Actually Memorize vs. What to Look Up

As a junior dev, you don't need to memorize OkHttp client configuration (DNS overrides, cipher suites, connection specs) — that's boilerplate you'll copy from past projects or generate with AI/docs.

What you do need to know cold, because it comes up in interviews and code reviews:

  • How to write a Retrofit interface from scratch (@GET, @POST, @Query, @Path, @Body)
  • The basic Retrofit.Builder() setup
  • Why suspend matters for network calls
  • The full request lifecycle, end to end, in your own words
  • Why the Repository pattern exists

Interview Questions on Retrofit

Easy

  • What is Retrofit? A type-safe HTTP client that turns annotated interface methods into network calls.
  • Difference between @Query and @Path? @Path substitutes a {placeholder} inside the URL; @Query appends ?key=value parameters.
  • Why use suspend fun for network calls? It moves the blocking work off the main thread via coroutines, avoiding UI freezes, without needing callbacks or Call<T>.

Medium

  • How does Retrofit relate to OkHttp? Retrofit is a layer on top of OkHttp. Retrofit handles interfaces, annotations, and response conversion; OkHttp handles the actual socket connection, headers, and request/response transport.
  • What is an Interceptor used for? Code that runs on every outgoing request or incoming response — commonly used for adding auth headers, logging, or retry logic.
  • Why wrap responses in a sealed class like Resource? It forces explicit handling of success, error, and loading states instead of relying on nullable types or exceptions leaking into the UI layer.
  • What does @SerializedName do, and when do you need it? Maps a JSON key to a Kotlin field with a different name — common when the API uses snake_case and your code uses camelCase.

Harder

  • Why mark the Retrofit instance as @Singleton in Hilt? Building Retrofit is expensive (thread pools, connection pools). A singleton ensures it's constructed once and reused, instead of rebuilt every time a screen or ViewModel is created.
  • How would you attach an auth token to every outgoing request? Add an OkHttp Interceptor that reads the token and appends an Authorization header before chain.proceed(request).
  • How would you test a Repository that depends on Retrofit? Mock the API interface (e.g., with MockK or a fake implementation) and inject it into the Repository, so tests don't hit the real network.

Closing Thoughts

Retrofit looks intimidating because tutorials usually only show the builder code in isolation. Once you trace a single request from the UI all the way to the server and back, the "magic" disappears — it's just a clean chain of responsibility: UI asks ViewModel, ViewModel asks Repository, Repository asks Retrofit, Retrofit asks the network.

Understand that chain, and you'll be able to explain — and debug — any Retrofit-based Android app, not just the one you built it in.