Skip to content

Android Retrofit Networking Explained - Complete Guide with Kotlin

DodaTech Updated 2026-06-29 3 min read

In this tutorial, you'll learn how to use Retrofit for Android networking: defining API interfaces, OkHttp configuration, error handling, caching, and coroutine integration.

What You'll Learn

how to use Retrofit for Android networking: defining API interfaces, OkHttp configuration, error handling, caching, and coroutine integration — Retrofit is the most popular networking library for Android. It reduces boilerplate, handles threading, and integrates seamlessly with coroutines and Serialization.

Why It Matters

Retrofit is the most popular networking library for Android. It reduces boilerplate, handles threading, and integrates seamlessly with coroutines and serialization.

Real-World Use

A weather app uses Retrofit to fetch forecasts from OpenWeatherMap, caches responses for offline use, handles 429 rate-limiting errors gracefully, and shows cached data while refreshing.

Learning Path

flowchart LR
    [Coroutines] --> [Retrofit Networking] --> [Room Database] --> [Offline-First]
    style 2 fill:#4CAF50,color:#fff

API Interface

interface WeatherApi {
    @GET("weather/current")
    suspend fun getCurrentWeather(@Query("city") city: String, @Query("units") units: String = "metric"): WeatherResponse
    @GET("weather/forecast/{city}")
    suspend fun getForecast(@Path("city") city: String, @Query("days") days: Int = 7): WeatherResponse
    @Multipart
    @PUT("weather/report")
    suspend fun uploadReport(@Part photo: MultipartBody.Part, @Part("description") description: RequestBody): ReportResponse
}

Expected output: The API interface defines endpoints with Retrofit annotations. Suspend functions enable coroutine integration.

Retrofit Client with OkHttp

object RetrofitClient {
    private const val BASE_URL = "https://api.weatherapp.com/v1/"
    private val okHttpClient = OkHttpClient.Builder()
        .addInterceptor(Interceptor { chain ->
            val request = chain.request().newBuilder()
                .addHeader("Authorization", "Bearer ${getToken()}")
                .addHeader("X-App-Version", BuildConfig.VERSION_NAME).build()
            chain.proceed(request)
        })
        .connectTimeout(30, TimeUnit.SECONDS)
        .readTimeout(30, TimeUnit.SECONDS)
        .cache(Cache(File(MyApplication.instance.cacheDir, "http_cache"), 10L * 1024 * 1024))
        .build()
    val weatherApi: WeatherApi by lazy {
        Retrofit.Builder().baseUrl(BASE_URL).client(okHttpClient)
            .addConverterFactory(GsonConverterFactory.create()).build().create(WeatherApi::class.java)
    }
}

Expected output: The Retrofit client is configured with auth headers, timeouts, and a 10MB cache.

ViewModel with Error Handling

class WeatherViewModel(application: Application) : AndroidViewModel(application) {
    private val api = RetrofitClient.weatherApi
    private val _state = MutableStateFlow<WeatherUiState>(WeatherUiState.Loading)
    val state: StateFlow<WeatherUiState> = _state.asStateFlow()
    fun loadWeather(city: String) {
        viewModelScope.launch {
            _state.value = WeatherUiState.Loading
            try {
                val response = api.getCurrentWeather(city)
                _state.value = WeatherUiState.Success(response)
            } catch (e: HttpException) {
                _state.value = WeatherUiState.Error(when (e.code()) {
                    401 -> "Invalid API key"; 404 -> "City not found"
                    429 -> "Rate limited"; else -> "Server error"
                })
            } catch (e: IOException) {
                _state.value = WeatherUiState.Error("Network error")
            }
        }
    }
}

Expected output: The ViewModel handles both HTTP errors and network exceptions with proper UI state management.

Common Errors

  1. EOFException at unexpected location - server closed connection early; check timeouts and retry logic
  2. SocketTimeoutException - server didn't respond in time; increase timeout or implement retry with backoff
  3. Moshi cannot adapt Kotlin classes - add KotlinJsonAdapterFactory() to your Moshi Builder
  4. HTTP 400 with no error body - enable logging interceptor to see the full request
  5. Path parameter type mismatch - @Path parameters must match the endpoint segment type

Practice Questions

  1. What is the difference between @Path, @Query, and @Body?

  2. How do you add authentication headers to every Retrofit request?

  3. What happens if you don't use the suspend modifier on a Retrofit method?

  4. How does OkHttp's interceptor chain work?

  5. When should you use Response instead of T as the return type?

Challenge

Build a GitHub user search app using Retrofit (api.github.com/search/users?q=query). Implement an interceptor that logs request timing. Show loading, error, and empty states. Handle pagination.

Real-World Task

Build an offline-first news app: Retrofit fetches articles from an API, caches via OkHttp cache, and persists in Room. Show cached articles immediately and refresh in background.

Frequently Asked Questions

{{< faq question="What is the difference between enqueue and execute?">}} execute() blocks the calling thread. enqueue() is async (background thread, main thread callback). With coroutines, use suspend functions. {{< /faq >}}

{{< faq question="How do I upload files with Retrofit?">}} Use @Multipart with @Part MultipartBody.Part for the file and @Part for other fields. {{< /faq >}}

{{< faq question="Can I use Retrofit with GraphQL?">}} Retrofit is REST-specific. Use Apollo Graphql client for GraphQL. You can use Retrofit for GET/POST to a GraphQL endpoint with raw JSON bodies. {{< /faq >}}

Security Tip: Never log auth tokens in production. Use HttpLoggingInterceptor at BODY level in debug only. Implement certificate pinning with OkHttp's CertificatePinner to prevent MITM attacks.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro