Development of modern mobile applications is almost impossible without active data exchange with remote servers. Retrofit has become the de facto industry standard for making HTTP requests in the Android ecosystem, combining ease of use and the power of the OkHttp library. Unlike low-level solutions, this tool allows you to describe interaction with the API in the form of familiar Java or Kotlin interfaces, which significantly speeds up the process of writing code.

You do not need to manually generate URL strings or parse raw server responses, since the library takes care of this routine. Retrofit automatically serializes and deserializes data, supporting various formats, such as JSON, XML or Protobuf. Understanding how this tool works is critical for any developer seeking to create stable and performant applications.

In this article we will take a detailed look at the process of integrating the library into a project, setting up the main components and handling typical network communication scenarios. You'll learn how to properly declare endpoints, pass parameters, and handle asynchronous responses while avoiding common architectural mistakes.

Preparing the project and adding dependencies

The first step in working with the network layer is to include the necessary libraries in your module's build file. Typically this is a file build.gradle (or build.gradle.kts) located in the app folder. You will need to add two main dependencies: yourself Retrofit and a converter for working with the format JSON, since it is the most common in web development.

In addition to the main libraries, it is highly recommended to connect an adapter for working with Coroutines or RxJava if your application is built on reactive programming. This will allow network requests to be executed asynchronously without blocking the main UI thread. Add the following lines to the section dependencies:

implementation 'com.squareup.retrofit2:retrofit:2.9.0'

implementation 'com.squareup.retrofit2:converter-gson:2.9.0'

implementation 'com.squareup.retrofit2:adapter-rxjava2:2.9.0'

After synchronizing the project, make sure that the Internet access permission is added to the application manifest (AndroidManifest.xml). Without the line <uses-permission android:name="android.permission.INTERNET" /> any attempts to connect to the server will fail with a security error and you will not be able to retrieve the data.

๐Ÿ’ก

Use the version of the library listed in the official documentation on GitHub, as version numbers may be updated and contain critical security fixes.

Creating and configuring a Retrofit instance

To get started, you need to create an instance of the class Retrofit, which will act as a factory for generating implementations of your interfaces. Typically, this object is initialized once at application startup and stored as a singleton to avoid wasting resources by repeatedly creating clients. The base URL is specified once, and all relative endpoint paths will be added to it automatically.

During the object construction process, you must specify a factory converter, which will be responsible for converting request and response bodies. If you are working with JSON, use GsonConverterFactory. Also at this stage, you can configure the client OkHttpClientby adding interceptors for logging requests or managing caching, which gives flexible control over network traffic.

An example of correct client initialization is as follows:

val retrofit = Retrofit.Builder()

.baseUrl("https://api.example.com/")

.addConverterFactory(GsonConverterFactory.create())

.build()

โš ๏ธ Attention: Make sure that the base URL ends with a slash /. If the slash is missing, the library may incorrectly concatenate the path to the resource, removing the last segment of the address.

โ˜‘๏ธ Checking the client settings

Done: 0 / 4

Description API interface and annotations

The heart of the Retrofit architecture is a Java or Kotlin interface that describes the desired behavior of the HTTP client. You define methods that correspond to specific operations and annotate them with special markers indicating the type of request (@GET, @POST, @PUT, @DELETE). The library automatically generates an implementation of this interface during app execution.

Special annotations are used to pass parameters to the request, such as @Path to substitute values โ€‹โ€‹directly into the URL, @Query to add query string parameters, and @Body to send complex objects in the body of the request. This allows you to make the code as declarative and readable as possible, hiding the complexity of generating HTTP messages.

Consider an example of an interface for working with user data:

interface ApiService {

@GET("users/{id}")

suspend fun getUser(@Path("id") userId: Int): Response

@POST("users/new")

suspend fun createUser(@Body user: UserCreateDto): Response

}

Using the keyword suspend in the method signature allows you to call this method directly from the coroutine, which makes the code asynchronous, but consistent in writing style. This is a modern approach that eliminates the need to work with cumbersome callbacks.

๐Ÿ“Š What approach to asynchrony do you use?
Corutins (Suspend)
RxJava
Callback
Live Data

Handling responses and data types

When executing requests, it is important to correctly interpret the server response. Interface methods can return an object Response<T>that encapsulates both success and HTTP error information. This gives the developer the flexibility to respond to different status codes, such as 404 (not found) or 500 (server error), without relying solely on exceptions.

If you are confident that the request should always succeed, you can return the data type directly (for example, simply User), but in this case any network errors will lead to throwing an exception that needs to be caught in the block try-catch. The choice of strategy depends on the reliability requirements of your application and the logic for handling failures.

The table below shows the main annotation methods for working with data:

Annotation Purpose Use example
@Path Replacing a variable in URL @Path("id") will replace {id}
@Query Query string parameters ?sort=asc will be added to the URL
@Body Request body (POST/PUT) Sending a JSON object
@Header Request headers Transfer of authorization token

The structure of the response often depends on the specifics of the backend. Sometimes the data comes wrapped in a common object with fields status and data. In such cases, it is necessary to create special wrapper classes (Data Class) in the application code so that the deserializer can correctly map JSON fields to the fields of the Kotlin object.

Asynchronous execution and coroutines

Network operations are blocking by nature, so their execution in the main thread (UI Thread) is strictly prohibited and will lead to an abnormal termination applications. Using coroutines allows you to run such tasks in the background using a dispatcher Dispatchers.IOand receive the result back in the main thread via Dispatchers.Main to update the interface.

Inside the_scope_ coroutine you call the API method and process the result. If the method returns Response, you check the property isSuccessful. If successful, you retrieve the response body via body(), and in case of error, parse the status code or error message to inform the user.

Example call in coroutine:

lifecycleScope.launch {

try {

val response = apiService.getUser(123)

if (response.isSuccessful) {

val user = response.body()

// Update UI

} else {

// Handle HTTP error

}

} catch (e: Exception) {

// Handling network failures

}

}

โš ๏ธ Attention: Always handle exceptions IOException and HttpException. The network is unreliable, and connection failure or server timeout are normal situations that the application should handle gracefully.
What is Dispatchers.IO?

It is a coroutine dispatcher optimized for performing I/O operations such as database reads or network requests. It uses a pool of threads, the size of which depends on the number of processor cores.

Advanced techniques and interceptors

To solve complex problems such as adding authorization tokens to each request, logging traffic, or retrying on failure, Retrofit uses OkHttp interceptors. An interceptor is an object that intercepts a request before it is sent and a response after it is received, allowing them to be modified on the fly.

The most common scenario is adding a header Authorization with a Bearer token. You create a class that implements the interface Interceptorand in the method intercept add the desired header to the new request. This interceptor is then added to the builder, which is passed to the builder. It is also useful to use a logging interceptor (for example, OkHttpClient, which is passed to the builder Retrofit.

It is also useful to use an interceptor for logging (eg HttpLoggingInterceptor), which outputs the full bodies of requests and responses to the console in the debug build. This is an indispensable tool when debugging problems of interaction with the server, allowing you to see exactly the data that goes to the network.

Remember that the network configuration may depend on the version of the server API or the userโ€™s region. The terms of access and data formats may change on the part of the service provider.

๐Ÿ’ก

Interceptors allow you to implement end-to-end functionality (cross-cutting), such as logging or authorization, without clogging the business logic code with repeated calls.

Frequently asked questions (FAQ)

What is the difference between returning Response and just T?

Return Response<T> gives you access to HTTP status code, headers, and allows you to handle errors (such as 404 or 500) as normal execution flow. Returning simply T means that any error (network or HTTP) will throw an exception that needs to be caught in a try-catch block.

How to cancel a running request in Retrofit?

If you use coroutines, cancellation of the request occurs automatically when canceling the scope of the coroutine (for example, when an Activity or ViewModel is destroyed). If Call objects are used, you can call the method cancel() on the Call instance.

Can Retrofit be used without the Internet?

Retrofit itself does not cache responses. However, if configured OkHttpClient using a cache and appropriate interceptors, it is possible to return cached data when there is no connection. This requires additional configuration at the client level.

How to send a multipart form (for example, uploading a photo)?

For this, an annotation is used @Multipart together with @Part. Type parameters MultipartBody.Part allow files to be transferred, and regular strings are sent as @Part("name") String.

Why does the SSLHandshakeException error occur?

This error usually means that the client does not trust the server's certificate. This could be due to a self-signed certificate in development or an expired certificate in production. To solve this, you need to configure SSLContext i TrustManager in OkHttp.