Table of Contents
Introduction
Retrofit is a popular networking library that makes it much easier to call web APIs from your Android app. Instead of manually building URLs, handling HTTP connections, and parsing raw responses, you describe your API in a simple Kotlin interface and let Retrofit do the heavy work.
This chapter gives you an overall understanding of how networking with Retrofit works. Detailed setup, API interface definitions, and error handling will each have their own chapters, so here you will focus on how the individual pieces fit together, how Retrofit interacts with the rest of your app, and the typical flow of a network request in an Android project.
What Retrofit Does in an Android App
Retrofit is a type safe HTTP client for Android. It helps you transform a remote REST API into a set of Kotlin functions that you can call like any other method in your code.
Instead of manually using HttpURLConnection or other low level tools, you describe each API endpoint as a function. Retrofit then:
- Builds the correct HTTP request based on the annotations you use.
- Sends the request over the network.
- Receives the HTTP response.
- Uses a converter to turn the raw response body into your Kotlin data classes.
- Returns either a successful result or an error that you can handle.
From your point of view, most Retrofit calls look like normal Kotlin function calls that either return a result or throw an exception, especially when you use them with coroutines.
Retrofit does not perform any network operations on the main thread. You must always call network requests from a background thread or use coroutines or other asynchronous mechanisms.
The Core Building Blocks of Retrofit
Retrofit uses several core building blocks that work together:
The first building block is the Retrofit instance itself, which you usually configure once in your app. This instance knows the base URL of your API and which converters to use for parsing responses, for example JSON to Kotlin objects.
The second building block is an API interface, which you will define in its own chapter. In this interface, each function matches one HTTP endpoint of your backend. You use annotations such as @GET, @POST, @Path, or @Query to describe how the request should be created.
The third building block is a converter, typically based on a library like Gson or Moshi. The converter takes the raw JSON text sent by the server and maps it into strongly typed Kotlin data classes that you define.
The fourth building block is a call adapter, which decides the type you get back from the API functions. Common choices are Call<T> for callbacks or suspend functions with coroutines. With coroutines, Retrofit works naturally with modern Android architecture patterns.
Finally, Retrofit works closely with an HTTP client under the hood. By default, it uses OkHttp, which handles the low level network operations, connection pooling, and features like interceptors.
How Retrofit Fits into Your App Architecture
Retrofit is usually placed in the data layer of your app. In a typical MVVM architecture, your flow will look like this:
At the bottom, a Retrofit API interface talks directly to your backend. Above that, a repository class uses this interface and applies any additional logic, such as caching, combining local and remote data, or mapping API models to domain models.
ViewModels then call the repository to request data. They never talk to Retrofit directly. Instead, they request operations like "load user profile" or "fetch posts," and the repository decides whether to call the network, local storage, or both.
UI components observe LiveData or other observable types exposed by the ViewModel. They receive data that has already been transformed from raw network responses into UI friendly models.
Retrofit therefore stays in one clear part of your codebase. This separation makes it easier to test your ViewModels and repositories, and to swap out or mock the network layer when necessary.
The Typical Retrofit Request Flow
A typical Retrofit request goes through a clear sequence of steps.
First, you create your Retrofit instance, usually with a base URL and a JSON converter. This is often done in a central location, such as a dependency injection module. The instance is built once and reused across the app.
Second, you create an implementation of your API interface using the Retrofit instance. Retrofit generates a concrete class behind the scenes when you call retrofit.create(YourApi::class.java).
Third, from your repository or another data layer class, you call the methods defined on your API interface. If you use coroutines, these are usually suspend functions that you call from a coroutine scope.
Fourth, Retrofit builds the HTTP request based on the annotations in your interface method, sends it through OkHttp, waits for the response, and uses the converter to parse it. If the response is successful and the format is correct, Retrofit gives you an object of the type you declared in the interface, for example User or List<Post>.
Fifth, your repository processes the result. This might include mapping network models to local models, saving them to a database, or combining them with other sources before returning them to the ViewModel.
Sixth, if an error occurs, Retrofit or the underlying HTTP client will throw an exception or provide an error response. Your repository is responsible for handling this gracefully and passing a meaningful state back to the ViewModel and UI, such as a wrapped result that includes both data and error information.
Asynchronous Behavior and the Main Thread
Since Android does not allow network access on the main thread, Retrofit requests are inherently asynchronous from the perspective of your UI.
With coroutines, you typically call Retrofit from a coroutine scope in the ViewModel or repository. The coroutine is suspended while the network request is running, and the main thread remains responsive. When the response arrives, execution resumes and the UI can be updated through LiveData or other observable structures.
If you use callback based APIs, Retrofit returns a Call<T> object. You then enqueue the call, and Retrofit will invoke your callback methods when a response or a failure occurs. This pattern is more verbose than using coroutines, which is why modern Android projects usually prefer suspend functions where possible.
Whichever approach you choose, the key idea is the same. Retrofit keeps your network work off the main thread and provides a clean way to handle results when they are ready.
Converters and Data Models
Converters are responsible for translating between the raw response body and your Kotlin data classes. Most Android apps that work with REST APIs use JSON as the data format. Retrofit itself does not know how to parse JSON, so it relies on a converter library.
You typically define a set of data classes that represent the JSON structures your backend returns. The converter reads the JSON keys and values and matches them to the properties in those data classes. This process depends on how you configure the converter and whether the JSON field names match your Kotlin property names.
In many real applications, you might create separate models for the network layer and for the domain or UI layer. Retrofit only needs to know about the network models. Other parts of your app can use different types that are more convenient for presentation or business logic.
Your Retrofit converter and data classes must accurately match the API's response format. If the JSON structure or field names do not align with your models, parsing will fail and you will get runtime errors.
Integrating Retrofit with Other Components
Retrofit does not work alone. In a complete Android app it interacts with several other components.
It often works together with a local database solution, such as Room. A common pattern is to fetch data from the network using Retrofit, save it to a Room database, and then observe the database from your UI components. This creates an offline friendly app that still syncs with the server when needed.
Retrofit also fits into your dependency injection setup. Libraries such as Hilt can provide the Retrofit instance, API interface, and related dependencies. This makes it easy to swap out the real API with a fake or mock implementation for testing.
For logging and debugging, you can combine Retrofit with OkHttp interceptors. Interceptors can log requests and responses, add headers, or apply common behavior to all network calls.
And finally, Retrofit works closely with your app's lifecycle. When you use it with architecture components and lifecycle aware tasks, you can avoid memory leaks and prevent network calls from continuing after a screen is destroyed.
Conclusion
Networking with Retrofit is about organizing your network layer so that calling a remote API feels as natural as calling a Kotlin function. By centralizing configuration in a Retrofit instance, describing endpoints in clean interfaces, and relying on converters and OkHttp for low level work, you gain a powerful yet simple way to communicate with servers.
In the upcoming chapters you will see how to set up Retrofit in your project, how to define API interfaces, and how to handle errors and edge cases in a robust way.