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

推荐订阅源

J
Java Code Geeks
Hugging Face - Blog
Hugging Face - Blog
博客园_首页
爱范儿
爱范儿
罗磊的独立博客
美团技术团队
Jina AI
Jina AI
量子位
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
酷 壳 – CoolShell
酷 壳 – CoolShell
有赞技术团队
有赞技术团队
V
V2EX
阮一峰的网络日志
阮一峰的网络日志
小众软件
小众软件
IT之家
IT之家
雷峰网
雷峰网
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 司徒正美
大猫的无限游戏
大猫的无限游戏
博客园 - 聂微东
月光博客
月光博客
人人都是产品经理
人人都是产品经理
博客园 - 三生石上(FineUI控件)

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.