Download as pdf or txt
Download as pdf or txt
You are on page 1of 40

What are GET, POST, PUT, PATCH, DELETE?

The GET method:


The GET method is used to retrieve data from the server. This is a read-only method, so
it has no risk of mutating or corrupting the data. For example, if we call the get method
on our API, we’ll get back a list of all to-dos.

The Post Method


The POST method sends data to the server and creates a new resource. The resource
creates it’s subordinate to some other parent resource. When a new resource is
POSTed to the parent, the API service will automatically associate the new resource by
assigning it an ID (new resource URI). In short, this method is used to create a new
data entry.
Note that we needed to pass in the request method, body, and headers. We did not
pass these in earlier for the GET method because by default these fields are configured
for the GET request, but we need to specify them for all other types of requests.

The Put Method


The PUT method is most often used to update an existing resource. If you want to
update a specific resource (which comes with a specific URI), you can call the PUT
method to that resource URI with the request body containing the complete new version
of the resource you are trying to update.

The Patch Method


The PATCH method is very similar to the PUT method because it also modifies an
existing resource. The difference is that for the PUT method, the request body contains
the complete new version, whereas for the PATCH method, the request body only
needs to contain the specific changes to the resource, specifically a set of instructions
describing how that resource should be changed, and the API service will create a new
version according to that instruction.

The Delete Method


The DELETE method is used to delete a resource specified by its URI.
Interceptors

What are Interceptors?


“According to documentation, Interceptors are a powerful mechanism that can
monitor, rewrite, and retry the API call. So basically, when we do some API call,
we can monitor the call or perform some tasks.”

We can use the interceptors to do so many things, for example, centrally monitor API
calls. Generally, we need to add the logger for each network call, but by using the
interceptor, we can add one logger centrally and that will work for all the network calls.
Another use case can be caching the response of network calls to build the offline-first
app

Types of Interceptors
We have two types of interceptors as follows:
● Application Interceptors: These are interceptors that are added between the
Application Code(our written code) and the OkHttp Core Library. These are the
ones that we add using addInterceptor().
● Network Interceptors: These are interceptors that are added between the
OkHttp Core Library and the Server. These can be added to OkHttpClient using
addNetworkInterceptor().
How to add interceptors in OkHttpClient?
While building the OkHttpClient object, we can add the interceptor as below:
fun myHttpClient(): OkHttpClient {
val builder = OkHttpClient().newBuilder()
.addInterceptor(/*our interceptor*/)
return builder.build()
}
Here, in addInterceptor, we pass the interceptor that we have created. Now, let's see how
to create the Interceptor.

Creating the Interceptor


To create the interceptor, we need to create a class by implementing the Interceptor
interface as below:
class MyInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
/**
* Our API Call will be intercepted here
*/
}
}
Here, in the intercept(), we can perform any action which we want inside it.
And to use the interceptor, we can use as below:
fun myHttpClient(): OkHttpClient {
val builder = OkHttpClient().newBuilder()
.addInterceptor(MyInterceptor())
return builder.build()
}

We can add the MyInterceptor in addInterceptor().


Now, let's discuss more real use cases where we can use the Interceptors.
Real use-cases using the Interceptors
The following are the common use cases in Android:
Logging the errors centrally
First, we need to create the ErrorInterceptor as below:
class ErrorInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request: Request = chain.request()
val response = chain.proceed(request)
when (response.code()) {
400 -> {
//Show Bad Request Error Message
}
401 -> {
//Show UnauthorizedError Message
}
403 -> {
//Show Forbidden Message
}
404 -> {
//Show NotFound Message
}
// ... and so on
}
return response
}
}
● First, we get the request from the chain.request()
● Then, we get the response returned from the server, by passing the request in
the chain.proceed(request)
● Now, we can check for the response code and perform an action.
● We can pass the error to the view.
● Let's say we get a 401 error i.e. Unauthorized then we can perform an action to
clear the app data/log out the user or any action which we want to perform.
Now, to use this ErrorInterceptor in our OkHttpClient, we can add as below:
.addInterceptor(ErrorInterceptor())
This is how we can create a centralized Error Logger using the Interceptor.
OkHttp has a built-in logger which is very useful for debugging.
Note: If we want to log the details for the redirection of the URL, consider using the
interceptor at the network layer using the addNetworkInterceptor().
Caching the response
If we want to cache the response of the API call so that if we call the API again, the
response comes out from Cache.
Let's say we have the API call from Client to Server and Cache-Control header is
enabled from the server, then OkHttp Core will respect that header and cache the
response for a certain time being which was sent from the server.
But what if the Cache-Control is not enabled from the server? We still can cache the
response from OkHttp Client using Interceptor.
Just see the above image. Here, what we have to do is that we have to intercept the
Response before going inside the OkHttp Core and add the header (Cache-Control), so
it will be treated as if the response(with the Cache-Control header) has come from the
server. Okhttp Core will respect that and cache the response.
We will create an interceptor as below:
class CacheInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val response: Response = chain.proceed(chain.request())
val cacheControl = CacheControl.Builder()
.maxAge(10, TimeUnit.DAYS)
.build()
return response.newBuilder()
.header("Cache-Control", cacheControl.toString())
.build()
}
}
Here, we have a CacheControl which is used to provide the header for Cache-Control.
Finally, we can add as below:
val okHttpClient = OkHttpClient().newBuilder()
.cache(Cache(File(applicationContext.cacheDir, "http-cache"), 10L * 1024L * 1024L)) //
10 MiB
.addNetworkInterceptor(CacheInterceptor())
.build();

Here, if we see, we are not using the addInterceptor() but using


addNetworkInterceptor() for the use case. This is because in this case, the operation is
happening at the network layer.
But, there is something important, we need to consider while building the offline-first
app.
The cached response will be returned only when the Internet is available as OkHttp is
designed like that.
● When the Internet is available and data is cached, it returns the data from the
cache.
● Even when the data is cached and the Internet is not available, it returns the
error "no internet available".
What to do now?
We can use the following ForceCacheInterceptor at the Application layer in addition to
the above one (CacheInterceptor, only if not enabled from the server). To implement in
the code, we will create a ForceCacheInterceptor as below:
class ForceCacheInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val builder: Request.Builder = chain.request().newBuilder()
if (!IsInternetAvailable()) {
builder.cacheControl(CacheControl.FORCE_CACHE);
}
return chain.proceed(builder.build());
}
}
We can add the Interceptor in OkHttpClient as below:
val okHttpClient = OkHttpClient().newBuilder()
.cache(Cache(File(applicationContext.cacheDir, "http-cache"), 10L * 1024L * 1024L)) //
10 MiB
.addNetworkInterceptor(CacheInterceptor()) // only if Cache-Control header is not
enabled from the server
.addInterceptor(ForceCacheInterceptor())
.build();

Here, we are adding the ForceCacheInterceptor to OkHttpClient using addInterceptor()


and not addNetworkInterceptor() as we want it to work on the Application layer.
Adding the Header like Access Token centrally
Let's say that we have to make the API calls and we have to add the Authorization
Header in all the API calls. Either we can use it individually or we can centralize that
using the Interceptor.
class AuthTokenInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val requestBuilder = originalRequest.newBuilder()
.header("Authorization", "AuthToken")
val request = requestBuilder.build()
return chain.proceed(request)
}
}
● First, we get the token for the header from our local storage like a
SharedPreference.
● Here, we intercept the original request which we triggered from the application
using chain.request() and set it to originalRequest.
● Then, we build the request again by adding the Header with the key and value
which is required to make the network call.
● Then, we will build the request again and return the response using
chain.proceed(request) by passing the new request which is having the
Authorization header.
This is how we can centralize the Header which is common in all the API Calls. Now to
use it in the OkHttpClient, we will do as below:
.addInterceptor(AuthTokenInterceptor())

Let's go to another use case.


Refreshing the Access Token at Single Place
Let us say we have a use-case that when we get a 401 error in the Error Interceptor and
we need to refresh the auth token as we have an Unauthorized error. We can do that
using the below:
override fun intercept(chain: Interceptor.Chain): Response {
val accessToken = //our access Token
val request = chain.request().newBuilder()
.addHeader("Authorization", accessToken)
.build()
val response = chain.proceed(request)
if (response.code() == 401) {
val newToken: String = //fetch from some other source
if (newToken != null) {
val newRequest = chain.request().newBuilder()
.addHeader("Authorization", newToken)
.build()
return chain.proceed(newRequest)
}
}
return response
}
If we get the response.code() as 401 i.e. Unauthorized, we will refresh the token here,
then modify the request by adding the new header and make the new request to the
server.
And, this should be synchronized to avoid the concurrency issue.
Note: Another way that is more flexible when it comes to refreshing the Access Token is
to use the Authenticator interface of OkHttp.
Now, let's move to another use case.
Enabling Gzip at Android End
Gzip is used for data compression. In Android as well, we can use the Gzip for the
compression using the interceptor.
So, while getting a response, OkHttp automatically respects the header (content
encoding) and decompresses the data and returns, but let's say when we have to send
compressed data to a server, then we have to write our own interceptor.
We can create the GzipRequestInterceptor class as below (taken from the OkHttp
repository).
public class GzipRequestInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
if (originalRequest.body() == null || originalRequest.header("Content-Encoding") !=
null) {
return chain.proceed(originalRequest);
}

Request compressedRequest = originalRequest.newBuilder()


.header("Content-Encoding", "gzip")
.method(originalRequest.method(),
forceContentLength(gzip(originalRequest.body())))
.build();
return chain.proceed(compressedRequest);
}

private RequestBody forceContentLength(final RequestBody requestBody) throws


IOException {
final Buffer buffer = new Buffer();
requestBody.writeTo(buffer);
return new RequestBody() {
@Override
public MediaType contentType() {
return requestBody.contentType();
}

@Override
public long contentLength() {
return buffer.size();
}

@Override
public void writeTo(BufferedSink sink) throws IOException {
sink.write(buffer.snapshot());
}
};
}
private RequestBody gzip(final RequestBody body) {
return new RequestBody() {
@Override
public MediaType contentType() {
return body.contentType();
}

@Override
public long contentLength() {
return -1; // We don't know the compressed length in advance!
}

@Override
public void writeTo(BufferedSink sink) throws IOException {
BufferedSink gzipSink = Okio.buffer(new GzipSink(sink));
body.writeTo(gzipSink);
gzipSink.close();
}
};
}
}

To use the interceptor we can use as below:


.addInterceptor(GzipRequestInterceptor())

HTTP STATUS CODES


The HTTP status code is a response made by the server to the client’s request. These
are three-digit codes, while there are more than 60 error status codes, we’ll go through
some of the major HTTP status codes which are printed generally.
Let’s discuss status codes with their meanings:

● 1xx – Informational Response (These status codes are all about the information
received by the server when a request is made).
● 2xx – Success (This status code depicts that the request made has been fulfilled
by the server and the expected response has been achieved).
● 3xx – Redirection (The requested URL is redirected elsewhere).
● 4xx – Client Errors (This indicates that the page is not found).
● 5xx – Server Errors (A request made by the client but the server fails to complete
the request).

Let’s go through some Major HTTP Status Codes:

1. 200 (Success/OK)
The HTTP status code 200 represents success which means the page you have
requested has been fetched. The action made has been accepted and has been
delivered to the client by delivering the requested page.

● GET: entity in reference to the requested source sent to the response


● POST: entity describing the response of action made
● HEAD: an entity-header field similar to the requested source
● TRACE: a request made by the client is taken care of by the server
2. 301 (Permanent Redirect)
The HTTP status code 301 means that the page you have requested has moved to a
new URL and which is permanent. In the future, whenever the user requests the same
website, it will be redirected to the new URL. The modified permanent URL is given by
the location filed in response.

3. 302 (Temporary Redirect)


The requested URL has been redirected to another website which is temporary. Major
changes in the URL will be in the future. Other than GET or HEAD, if the 302 is received
in response to a request. The redirection is temporarily redirected to another website.

4. 304 (Not Modified)


HTTP status code 304 is used for caching purposes. The response has not been
changed so that in the future the client can resume the same cache. If there’s a GET
request and access is allowed, but the document has not been modified. The response
MUST NOT contain a message-body and therefore comes to an end by the first empty
line.

5. 400 (Bad Request)


When the client requests a page and the server is not able to understand anything, it
displays a 400 HTTP status code. The client SHOULD NOT repeat the request without
any changes. The request can be a malformed, deceptive request routing, or invalid
request.

6. 401 (Unauthorized Error)


This HTTP status code requires user authentication. The response includes the
WWW-Authenticate header field containing a challenge applied to a requested source.
HTTP access authentication “HTTP Authentication: Basic and Digest Access
Authentication”.

7. 403 (Forbidden)
The HTTP status code 403 implies that the request is understood by the server, but still
refuses to fulfill it. If the request method was not HEAD and also the server wants to
make it public when the request is not completed, it SHOULD tell the reason for the
refusal in the entity.
8. 404 (Not Found)
404 HTTP Status code appears when you request a URL and then the server has not
found anything. This happens when the server doesn’t wish to describe the reason why
the request has been refused. Also, the server is not able to find a representation for the
target resource.

9. 500 (Internal Server Error)


500 HTTP status code means requesting a URL is not fulfilled because the server
encounters an unexpected condition. It gives information about the request made if it is
successful, and throws an error. When there’s an error during a connection to the
server, and the requested page cannot be accessed then this message is displayed.

10. 501 (Not Implemented)


When a request is made by the client, the server is not able to recognize the request
method and is not able to support any resource. By default. it is cacheable. The
methods, the server requires to support are GET and HEAD. The server does not
support the functionality required to fulfill the request.

Other HTTP Status Codes Are:


100 Continue
101 Switching Protocol
102 Processing (WebDAV)
103 Early Hints
200 OK
201 Created
202 Accepted
203 Non-Authoritative Information
204 No Content
205 Reset Content
206 Partial Content
207 Multi-Status (WebDAV)
208 Already Reported (WebDAV)
226 IM Used (HTTP Delta encoding)
300 Multiple Choice
301 Moved Permanently
302 Found
303 See Other
304 Not Modified
305 Use Proxy
306 Unused
307 Temporary Redirect
308 Permanent Redirect
400 Bad Request
401 Unauthorized
402 Payment Required
403 Forbidden
404 Not Found
405 Method Not Allowed
406 Not Acceptable
407 Proxy Authentication Required
408 Request Timeout
409 Conflict
410 Gone
411 Length Required
412 Precondition Failed
413 Payload Too Large
414 URI Too Long
415 Unsupported Media Type
416 Range Not Satisfiable
417 Expectation Failed
418 I’m a teapot
421 Misdirected Request
425 Too Early
426 Upgrade Required
428 Precondition Required
429 Too Many Requests
431 Request Header Fields Too Large
451 Unavailable For Legal Reasons
500 Internal Server Error
501 Not Implemented
502 Bad Gateway
503 Service Unavailable
504 Gateway Timeout
505 HTTP Version Not Supported
506 Variant Also Negotiates
507 Insufficient Storage (WebDAV)
508 Loop Detected (WebDAV)
510 Not Extended
511 Network Authentication Required
Retrofit with Kotlin Coroutines
I will be using the following project for the implementation part. The project follows a
basic MVVM Architecture for simplicity. You can find the complete code for the
implementation mentioned in this blog in the project itself.
First, we need to set up our dependencies for the Retrofit as below:

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

Note: Always check for the latest available version.


As I am using the Gson to parse JSON into Java and Kotlin classes, I have added the
dependency for the Gson. You can add based on your requirement.
Now, create the data class ApiUser as below:
data class ApiUser(
@SerializedName("id")
val id: Int = 0,
@SerializedName("name")
val name: String = "",
@SerializedName("email")
val email: String = "",
@SerializedName("avatar")
val avatar: String = ""
)

Now, we need to create the ApiService interface required for Retrofit.


interface ApiService {

@GET("users")
suspend fun getUsers(): List<ApiUser>

@GET("more-users")
suspend fun getMoreUsers(): List<ApiUser>

@GET("error")
suspend fun getUsersWithError(): List<ApiUser>

}
Note: We have used suspend keyword to support Coroutines so that we can call it from
a Coroutine or another suspend function.
After this, we will be needing a class RetrofitBuilder which will be a Singleton.
object RetrofitBuilder {

private const val BASE_URL = "https://5e510330f2c0d300147c034c.mockapi.io/"

private fun getRetrofit(): Retrofit {


return Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
}

val apiService: ApiService = getRetrofit().create(ApiService::class.java)

Then, we will create an interface ApiHelper.


interface ApiHelper {

suspend fun getUsers(): List<ApiUser>

suspend fun getMoreUsers(): List<ApiUser>


suspend fun getUsersWithError(): List<ApiUser>

interface ApiHelper {

suspend fun getUsers(): List<ApiUser>

suspend fun getMoreUsers(): List<ApiUser>

suspend fun getUsersWithError(): List<ApiUser>

Note: Again, we have used suspend keyword to support Coroutines so that we can call
it from a Coroutine or another suspend function.
After that, we will create a class ApiHelperImpl that implements the ApiHelper interface.
class ApiHelperImpl(private val apiService: ApiService) : ApiHelper {

override suspend fun getUsers() = apiService.getUsers()

override suspend fun getMoreUsers() = apiService.getMoreUsers()

override suspend fun getUsersWithError() = apiService.getUsersWithError()

Once we've done that, we can create the instance of ApiHelper as below:
val apiHelper = ApiHelperImpl(RetrofitBuilder.apiService)
Finally, we can pass this instance wherever required, for example to the ViewModel,
and make the network call to get the users from the network as below:

class SingleNetworkCallViewModel(private val apiHelper: ApiHelper, private val


dbHelper: DatabaseHelper) : ViewModel() {

init {
fetchUsers()
}

private fun fetchUsers() {


viewModelScope.launch {
try {
val usersFromApi = apiHelper.getUsers()
// list of users from the network
} catch (e: Exception) {
// handle exception
}
}
}

This way, we are able to fetch the data from the network using Retrofit with Kotlin
Coroutines in Android.
I must mention that you can learn much more from the GitHub repository that I
mentioned above in this blog. You can learn the following:
● Making network calls in series using Retrofit with Kotlin Coroutines.
● Making multiple network calls in parallel using Retrofit with Kotlin Coroutines.
This is how we can use Retrofit with Kotlin Coroutines in Android.
Retrofit with Kotlin Flows
First, we need to set up our dependencies for the Retrofit as below:

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

Note: Always check for the latest available version.


As I am using the Gson to parse JSON into Java and Kotlin classes, I have added the
dependency for the Gson. You can add based on your requirement.
Now, create the data class ApiUser as below:

data class ApiUser(


@SerializedName("id")
val id: Int = 0,
@SerializedName("name")
val name: String = "",
@SerializedName("email")
val email: String = "",
@SerializedName("avatar")
val avatar: String = ""
)

Now, we need to create the ApiService interface required for Retrofit.

interface ApiService {

@GET("users")
suspend fun getUsers(): List<ApiUser>

@GET("more-users")
suspend fun getMoreUsers(): List<ApiUser>
@GET("error")
suspend fun getUsersWithError(): List<ApiUser>

Note: We have used suspend keyword.


After this, we will be needing a class RetrofitBuilder which will be a Singleton.

object RetrofitBuilder {

private const val BASE_URL = "https://5e510330f2c0d300147c034c.mockapi.io/"

private fun getRetrofit(): Retrofit {


return Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
}

val apiService: ApiService = getRetrofit().create(ApiService::class.java)

Then, we will create an interface ApiHelper.

interface ApiHelper {

fun getUsers(): Flow<List<ApiUser>>

fun getMoreUsers(): Flow<List<ApiUser>>


fun getUsersWithError(): Flow<List<ApiUser>>

Note: The return type is Flow.


After that, we will create a class ApiHelperImpl that implements the ApiHelper interface.

class ApiHelperImpl(private val apiService: ApiService) : ApiHelper {

override fun getUsers() = flow {


emit(apiService.getUsers())
}

override fun getMoreUsers() = flow {


emit(apiService.getMoreUsers())
}

override fun getUsersWithError() = flow {


emit(apiService.getUsersWithError())
}

Here, we must understand that the return type is Flow. Also, we are using a flow builder
and emitting the item as per the requirement.
Once we've done that, we can create the instance of ApiHelper as below:
val apiHelper = ApiHelperImpl(RetrofitBuilder.apiService)

Finally, we can pass this instance wherever required, for example to the ViewModel,
and make the network call to get the users from the network as below:
class SingleNetworkCallViewModel(private val apiHelper: ApiHelper, private val
dbHelper: DatabaseHelper) : ViewModel() {

init {
fetchUsers()
}

private fun fetchUsers() {


viewModelScope.launch {
apiHelper.getUsers()
.flowOn(Dispatchers.IO)
.catch { e ->
// handle exception
}
.collect {
// list of users from the network
}
}
}

This way, we are able to fetch the data from the network using Retrofit with Kotlin Flow
in Android.
I must mention that you can learn much more from the GitHub repository that I
mentioned above in this blog. You can learn the following:
● Making network calls in series using Retrofit with Kotlin Flow.
● Making multiple network calls in parallel using Retrofit with Kotlin Flow.
This is how we can use Retrofit with Kotlin Flow in Android.
Retrofit with Multipart
In networking, a multipart request is a type of HTTP request that allows you to send
multiple types of data in a single request. This can be useful when you need to upload
files, such as images or videos, along with other data, such as text or metadata.
A multipart request is structured as a series of parts, where each part contains a
different type of data. Each part is separated by a boundary string, which is defined in
the request headers. The boundary string must be unique and not occur in any of the
data being sent.
When the server receives a multipart request, it uses the boundary string to identify the
beginning and end of each part. It can then process each part separately, extracting the
data and metadata as needed.
To create a multipart request, you can use a library such as OkHttp or Volley. Both of
these libraries provide methods for creating multipart requests and uploading files.
In Interface we have to declare the following method

@Multipart
@POST("auth/signup")
suspend fun signup(@Part part: MultipartBody.Part?,
@Part("phoneNumber") phoneNumber: RequestBody,
@Part("email") email: RequestBody,
@Part("firstName") firstName: RequestBody,
@Part("lastName") lastName: RequestBody,
@Part("otp") otp: RequestBody,
): Response<ServerResponse<SignUpResponse>>

suspend fun signUp(file: File?, firstName: String, lastName: String,


email: String, phoneNumber: String, otp: String): Any {
val builder = MultipartBody.Builder()
val requestFile: RequestBody =
RequestBody.create("multipart/form-data".toMediaTypeOrNull(), file!!)
val body: MultipartBody.Part = MultipartBody.Part.createFormData("upload_file",
file.name, requestFile)
val firstNameVal: RequestBody =
RequestBody.create("multipart/form-data".toMediaTypeOrNull(), firstName)
val lastNameVal: RequestBody =
RequestBody.create("multipart/form-data".toMediaTypeOrNull(), lastName)
val emailVal: RequestBody =
RequestBody.create("multipart/form-data".toMediaTypeOrNull(), email)
val phoneNumberVal: RequestBody =
RequestBody.create("multipart/form-data".toMediaTypeOrNull(), phoneNumber)
val otpVal: RequestBody =
RequestBody.create("multipart/form-data".toMediaTypeOrNull(), otp)
return try {
requestClone { formService.signup(body, phoneNumberVal, emailVal, firstNameVal,
lastNameVal, otpVal) }
} catch (ex: Exception) {
nullSafeErrorLogging(ex.localizedMessage)
}
}

Read Write Timeout


1. Default timeouts
By default, Retrofit 2 uses the following timeouts:
1. Call timeout – 0 (no timeout)
2. Connection timeout – 10 seconds
3. Read timeout – 10 seconds
4. Write timeout – 10 seconds
2. Set timeouts using OkHttpClient.Builder
2.1. Timeout methods
OkHttpClient.Builder API provides 4 methods which can be used to set timeouts.
● callTimeout(Duration duration) – Sets the default timeout for complete calls.
The call timeout spans the entire call: resolving DNS, connecting, writing the
request body, server processing, and reading the response body. If the call
requires redirects or retries all must complete within one timeout period.The
default value is 0 which imposes no timeout.
● connectTimeout(Duration duration) – Sets the default connect timeout for new
connections. The connect timeout is applied when connecting a TCP socket to
the target host.
● readTimeout(Duration duration) – The read timeout is applied to both the TCP
socket and for individual read IO operations including on Source of the
Response.
● writeTimeout(Duration duration) – The write timeout is applied for individual
write IO operations.
All above methods are overloaded methods and can accept either Duration or two
parameters i.e. time out number, time unit. For example, call timeout can be configured
using callTimeout(long timeout, TimeUnit unit) also.
2.2. How to set timeout
Java example code to set timeout duration in Retrofit in any android app.
String BASE_URL = "https://howtodoinjava.com/";

OkHttpClient.Builder httpClient = new OkHttpClient.Builder()


.callTimeout(2, TimeUnit.MINUTES)
.connectTimeout(20, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS);

Retrofit.Builder builder = new Retrofit.Builder()


.baseUrl(BASE_URL)
.addConverterFactory(SimpleXmlConverterFactory.create());

builder.client(httpClient.build());

Retrofit retrofit = builder.build();

//Create service
RssService rssService = retrofit.create(RssService.class);
3. How to handle retrofit connection timeout exception
Generally in the android app, we do not care which type of timeout error has occurred
because it all boils down to slow network connection.
In app, in case of network timeouts, can check for the class of exception instance when
the error finally timeouts and onFailure(Throwable t) is executed. We shall check for
SocketTimeoutException and IOException, specially.

4. What different timeouts mean?

4.1. Call timeout


It is the sum of all the time taken to complete the request. It includes time taken in
resolving DNS, establishing connection, sending request (including payload) and
receiving response (including payload).
If there is some time taken in server processing that is also included in this call time.
We should configure call timeout to a large value for above said reasons.

4.2. Connect timeout


Connection timeout is the time that start from sending the request to a completed TCP
handshake with the server. If Retrofit couldn’t establish the connection to the server
within the set connection timeout limit, request is considered as failed.
A connection timeout may be set large for countries with bad Internet connection.

4.3. Read timeout


The read timeout is the time-out applied from the moment you have established a
connection (So handshaking is done, and the connection can be used).
Specifically, if the server fails to send a byte in specified timeout period after the last
byte, a read timeout error will be raised.

4.4. Write timeout


If sending a single byte takes longer than the configured write timeout limit the a read
timeout error will be raised by retrofit.
We can set larger timeouts for users with bad internet connections.
Retrofit Caching

Let's understand how caching is going to help us in our Android applications.

● When we make a network call to fetch the data from the server.
● For the very first time, it will get the data from the server and it will cache the
HTTP response on the client.
● Then, if we make the same API call again, it will return the data from the cache
instantly.

This way, our Android applications can do two very important things:

● Work even if there is no internet connection. This will helps us in building the
offline-first apps.
● Work faster as the response is cached locally.

Now, let's learn how to enable caching in OkHttp and Retrofit. Before that, we need to
understand that Retrofit uses the OkHttp client for HTTP operations, which means that
whatever we have to do to enable caching, we need to do with the OkHttp. We do not
have to do anything extra for Retrofit as it will use our configured OkHttp client.
Things become easier when we already have the Cache-Control header enabled from
the server, then OkHttp will respect that header and cache the response for a specific
time that is being sent from the server.
But what if the Cache-Control is not enabled from the server? We can still cache the
response from OkHttp Client using Interceptor. We will learn how.
For this, we need to implement Interceptor like below:
class CacheInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val response: Response = chain.proceed(chain.request())
val cacheControl = CacheControl.Builder()
.maxAge(10, TimeUnit.DAYS)
.build()
return response.newBuilder()
.header("Cache-Control", cacheControl.toString())
.build()
}
}
Here, we created CacheInterceptor by implementing Interceptor and we have a
CacheControl builder that is used to provide the header for the Cache-Control.
Then, we need to add this CacheInterceptor to the OkHttpClient using
addNetworkInterceptor.
val okHttpClient = OkHttpClient().newBuilder()
.cache(Cache(File(applicationContext.cacheDir, "http-cache"), 10L * 1024L * 1024L)) //
10 MiB
.addNetworkInterceptor(CacheInterceptor())
.build();
After that, we can use this okHttpClient directly or with Retrofit.
But, there is a catch that we need to understand while building the offline-first app.
OkHttp is designed in such a way that it returns the cached response only when
the Internet is available.

● It returns the data from the cache only when the Internet is available and the data
is cached.
● It returns with the error "no internet available" even when the data is cached and
but the Internet is not available.

How to solve this issue?


We can create a ForceCacheInterceptor in addition to the above one
(CacheInterceptor, only if Cache-Control header is not enabled from the server).
class ForceCacheInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val builder: Request.Builder = chain.request().newBuilder()
if (!IsInternetAvailable()) {
builder.cacheControl(CacheControl.FORCE_CACHE);
}
return chain.proceed(builder.build());
}
}
Then, add the interceptor in OkHttpClient like below:
val okHttpClient = OkHttpClient().newBuilder()
.cache(Cache(File(applicationContext.cacheDir, "http-cache"), 10L * 1024L * 1024L)) //
10 MiB
.addNetworkInterceptor(CacheInterceptor()) // only if Cache-Control header is not
enabled from the server
.addInterceptor(ForceCacheInterceptor())
.build();
Here, we need to notice that we are adding ForceCacheInterceptor to OkHttpClient
using addInterceptor() and not addNetworkInterceptor().

● addInterceptor: used to add the interceptor at the application level.


● addNetworkInterceptor: As the name says, used to add the interceptor at the
network level.
After that, we can use this okHttpClient directly or with Retrofit and it will work as
expected.
This is how we can cache HTTP responses in Android using OkHttp Interceptor and
Retrofit for building offline-first Android apps.

OAuth2.0 - Refresh and Access Token


Retrofit is a type-safe HTTP client by Square that was built for the Android platform. It
offers an easy and clean way to make REST API network calls and parses the
JSON/XML response(s) into Java Objects which we can then use in our app.
As a security measure, most API access points require users to provide an
authentication token that can be used to verify the identity of the user making the
request so as to grant them access to data/ resources from the backend. The client app
usually fetches the token upon successful login or registration then saves the token
locally and appends it to subsequent requests so that the server can authenticate the
user.

What are Access and Refresh Tokens?


Now, let’s understand the tokens. When a user successfully authenticates in our
backend, we provide him two tokens. Access Token, and Refresh Token.
Access Token is mandatory for every other request that requires authentication and with
Access Token we identify the authenticated user in the backend. If the access token is
expired then we may use the refresh token to generate the access token again without
prompting the user to login again.
To generate a new access token using refresh token, we have another api endpoint, in
my case it is /refresh-token and we will pass the refresh token to this api and it will
return us the new access_token.
Now let’s understand how we can use it in our Android Project.
But before moving I hope you know the basics about Retrofit and Hitting APIs from the
android side.

Retrofit Authenticator Refresh Token


I hope you already have a Retrofit and networking setup in your project. After this the
first thing that we have to do is, we need to create an interface to define our
refresh-token api call.
interface TokenRefreshApi : BaseApi {
@FormUrlEncoded
@POST("auth/refresh-token")
suspend fun refreshAccessToken(
@Field("refresh_token") refreshToken: String?
): TokenResponse
}
This api call will give us the new access_token and refresh_token .
data class TokenResponse(
val access_token: String?,
val refresh_token: String?
)
Now we need to build an Authenticator
class TokenAuthenticator(
context: Context,
private val tokenApi: TokenRefreshApi
) : Authenticator, BaseRepository(tokenApi) {

private val appContext = context.applicationContext


private val userPreferences = UserPreferences(appContext)

override fun authenticate(route: Route?, response: Response): Request? {


return runBlocking {
when (val tokenResponse = getUpdatedToken()) {
is Resource.Success -> {
userPreferences.saveAccessTokens(
tokenResponse.value.access_token!!,
tokenResponse.value.refresh_token!!
)
response.request.newBuilder()
.header("Authorization", "Bearer
${tokenResponse.value.access_token}")
.build()
}
else -> null
}
}
}

private suspend fun getUpdatedToken(): Resource<TokenResponse> {


val refreshToken = userPreferences.refreshToken.first()
return safeApiCall { tokenApi.refreshAccessToken(refreshToken) }
}
}

Here you can see we have a function called authenticate() , this function will be called
every time your api will return a 401 error; that means unauthenticated .
Inside this function we are calling the function getUpdatedToken() . This function will
either get the new tokens successfully, or we may get an error. In case we get the token
successfully, we will update the tokens in our local storage. And we will also build a new
request, with the updated access_token . In case we get error, we will return null so that
our api will not just keep trying to refresh the token.
Now we will use this Authenticator, to our Retrofit Client.
class RemoteDataSource {

//My Base URL


companion object {
private const val BASE_URL = "http://simplifiedcoding.tech/mywebapp/public/api/"
}

/*
* This function will build the API
* inside this function we are creating our TokenAuthenticator instance
* */
fun <Api> buildApi(
api: Class<Api>,
context: Context
): Api {
val authenticator = TokenAuthenticator(context, buildTokenApi())
return Retrofit.Builder()
.baseUrl(BASE_URL)
.client(getRetrofitClient(authenticator))
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(api)
}

/*
* This function will build the TokenRefreshApi
* I have a separate interface that contains only the
* TokenRefresh endpoint and this function will build that api
* */
private fun buildTokenApi(): TokenRefreshApi {
return Retrofit.Builder()
.baseUrl(BASE_URL)
.client(getRetrofitClient())
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(TokenRefreshApi::class.java)
}

/*
* This function will build our OkHttpClient
* As we need it to intercept the request and add
* required parameters
* Also to add TokenAuthenticator, that will get the refresh token
* we need this OkHttp Client
* As you can see we are passing an Authenticator to the function
* */
private fun getRetrofitClient(authenticator: Authenticator? = null): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor { chain ->
chain.proceed(chain.request().newBuilder().also {
it.addHeader("Accept", "application/json")
}.build())
}.also { client ->
authenticator?.let { client.authenticator(it) }
if (BuildConfig.DEBUG) {
val logging = HttpLoggingInterceptor()
logging.setLevel(HttpLoggingInterceptor.Level.BODY)
client.addInterceptor(logging)
}
}.build()
}
}
And that’s it. Now everytime you will get a 401 error, authenticator will try to refresh the
token. If refresh succeeded, your user will not be logged out, and if it failed user will be
logged out.
Interview Questions
1. What is Retrofit?
Retrofit is a type of library that allows you to connect to a REST API in your Android
app. It makes it easy to parse JSON data and to make network requests, and it also
provides some nice features like caching and automatic retries.

Retrofit is a type-safe HTTP client for Android and Java.

2. How do you install Retrofit in Android?


You can install Retrofit using Gradle. First, add the following dependency to your
build.gradle file:

compile ‘com.squareup.retrofit2:retrofit:2.1.0’

Then, add the following to your AndroidManifest.xml file:

Finally, add the following to your proguard-rules.pro file:

-keepattributes Signature
-keepattributes Exceptions
-keep class com.squareup.okhttp3.** { *; }
-keep interface com.squareup.okhttp3.** { *; }
-dontwarn com.squareup.okhttp3.**

3. Can you explain the architecture of Retrofit?


Retrofit is a type-safe HTTP client for Android and Java. It makes it easy to consume
JSON or XML data from a web service. It is built on top of the OkHttp library. Retrofit
uses annotations to define how to map an endpoint to a Java interface. It can also be
used with RxJava to return observable objects.

4. What are the advantages and disadvantages of using Retrofit?


The main advantage of using Retrofit is that it makes it easy to connect to a REST API
and retrieve data. The main disadvantage is that it can be difficult to set up, especially if
you are not familiar with REST APIs.

5. What is the difference between a GET request and a POST request?


A GET request is a request for data from a server. A POST request is a request to
create or update data on a server.
6. Can you give me an example of how to use @Path annotation with Retrofit?
The @Path annotation is used to specify a path parameter in the URL. For example, if
you have a URL like http://example.com/users/{userId}, then you can use
@Path(“userId”) to specify that the userId parameter should be replaced with the actual
value.

7. What does the @QueryMap annotation do in Retrofit?


The @QueryMap annotation allows you to specify a map of query parameters for a
request. This is useful for cases where you want to specify a large number of optional
parameters for a request.

8. How can you pass query parameters when making an HTTP request using
Retrofit?
When making an HTTP request using Retrofit, you can pass query parameters by
annotating the request method with @Query. The query parameters will be appended to
the URL.

9. What are some ways to handle error conditions when using Retrofit?
There are a couple of ways to handle error conditions when using Retrofit. One way is
to use a custom ErrorHandler. This will give you the ability to handle errors in a way that
makes sense for your application. Another way to handle errors is to use the @Retry
annotation. This will cause Retrofit to automatically retry the request a specified number
of times before giving up.

10. How can you add custom headers to your requests using Retrofit?
By default, Retrofit will add any headers specified in your @Headers annotation to every
request. If you want to add headers only for a single request, you can use the
@Headers annotation on that request.

11. How do you set up multiple endpoints for your API calls using Retrofit?
You can set up multiple endpoints for your API calls using Retrofit by creating a new
instance of the Retrofit class for each endpoint. For example, if you have two endpoints,
one for production and one for development, you would create two Retrofit instances,
one for each endpoint. You would then create a new Service interface for each
endpoint, and each Service would have its own methods for making API calls.

12. Is it possible to define multiple base URLs for your application? If yes, then
how?
Yes, it is possible to define multiple base URLs for your application using Retrofit. This
can be accomplished by using the @Url annotation on your Retrofit interface methods.
This annotation takes a String value which represents the URL that you want to use for
the specific method call.

13. Why do you think you might need to create custom converters?
There are a few reasons you might need to create custom converters. One reason is if
you are working with a legacy API that doesn’t return data in a format that Retrofit can
work with by default. Another reason is if you want more control over how the data is
converted from its raw format into Java objects. Creating custom converters gives you
the ability to fine-tune this process to better suit your needs.

14. What’s the best way to parse XML responses from a server with Retrofit?
The best way to parse XML responses from a server with Retrofit is to use a
SimpleXmlConverter. This converter will take care of all of the heavy lifting for you and
will allow you to focus on other aspects of your project.

15. How do you handle authentication when using Retrofit?


When using Retrofit for authentication, there are a few different approaches that can be
taken. One approach is to use an interceptor, which can be used to add an
authentication header to every request that is made. Another approach is to use a
RequestInterceptor, which can be used to add an authentication header to specific
requests. Finally, you can also use a Authenticator, which can be used to handle
authentication errors.

16. Do you have any experience working with RxJava-based Reactive


Programming using Retrofit?
Yes, I have experience working with RxJava-based Reactive Programming using
Retrofit. I have used it to build Android applications that consume data from RESTful
web services.

17. What would you recommend as the best practices for creating clean
interfaces when using Retrofit?
I would recommend a few things when it comes to creating clean interfaces with
Retrofit. First, make sure that your interface methods are well-named and clearly
indicate what they are doing. Second, use annotations to clearly specify the HTTP
method and endpoint for each interface method. Finally, use Retrofit’s built-in converter
methods to deserialize your data into POJOs.
18. How do you handle asynchronous operations when using Retrofit?
When using Retrofit, you can use the @retrofit annotation to specify that a method is
asynchronous. This annotation will cause the Retrofit library to automatically create a
new thread for the method call and will return a Call object that can be used to track the
status of the operation.

19. How do you get access to the response body when using Retrofit?
When you are using Retrofit, you can get access to the response body by using the
@Body annotation. This annotation will take the response body and convert it into a
Java object for you to use.

20. What is the role of Jackson Converter in Retrofit?


The Jackson Converter is responsible for converting Java objects into JSON format and
vice versa. This is important because it allows Retrofit to interface with a variety of
different backends.

21. Can you explain how to use Retrofit in an Android application?


Retrofit is a library that can be used on Android to simplify the process of making
network requests. It can be used to make requests to RESTful web services, and can
also be used to make requests to other types of web services. To use Retrofit in an
Android application, you first need to add the library to your project. Then, you need to
create an interface that defines the methods that you want to use to make requests.
Finally, you need to create a Retrofit instance and use it to make requests.

22. How do you set up a new project with Retrofit in it?


The first step is to add the Retrofit library to your project dependencies. Then, you need
to create an interface that defines the endpoints you want to call. Finally, you need to
create a Retrofit instance and use it to generate an implementation of your interface.

23. What are the main components of a REST API call?


The main components of a REST API call are the URL, the HTTP method, the headers,
and the body. The URL is the address of the server that you are trying to access. The
HTTP method is the type of request that you are making to the server, such as GET,
POST, PUT, or DELETE. The headers are the additional information that you are
sending along with the request. The body is the data that you are sending to the server.

24. Can you give me some examples of real-world applications that make
extensive use of Retrofit?
Some examples of real-world applications that use Retrofit extensively are the Android
applications for Netflix, Airbnb, and Yelp. All of these apps need to communicate with a
backend server in order to function, and Retrofit makes it easy to set up this
communication.

25. Why should we use Retrofit over other libraries like Volley or OkHttp?
Retrofit is a great library for making network requests on Android because it is easy to
use and has a lot of features. With Retrofit, you can easily add query parameters, add
headers, and specify the request body all in one place. Retrofit also makes it easy to
parse JSON responses and supports both synchronous and asynchronous requests.

26. Is it possible to customize JSON parsing using Retrofit? If yes, then how?
Yes, it is possible to customize JSON parsing using Retrofit. This can be done by
creating a custom converter that extends the Converter.Factory class. This custom
converter can then be used to parse the JSON response into the desired format.

27. What’s the best way to send an image using Retrofit?


The best way to send an image using Retrofit is to first encode the image into a Base64
string, and then send that string as a parameter in the Retrofit request.

28. When would you consider using Volley instead of Retrofit?


Volley is a good choice if you need a light-weight solution or if you need to support older
versions of Android. Retrofit is a good choice if you’re looking for a more feature-rich
library.

29. Does Retrofit support caching? If so, how does it work?


Yes, Retrofit supports caching. When you make a request, Retrofit will first check if
there is a cached response for that request. If there is, it will return the cached
response. If there is no cached response, Retrofit will make the network request and
cache the response for future requests.

30. What do you understand about GSON annotations?


GSON annotations are used to specify how a field should be serialized or deserialized
by the GSON library. For example, the @SerializedName annotation can be used to
specify the name of a field in the JSON representation, while the @Expose annotation
can be used to specify which fields should be included in the JSON representation.

31. Can you explain what annotations are used for and which ones you have
experience working with?
Annotations are a way of adding metadata to Java code. They can be used for a variety
of purposes, such as specifying that a certain method should be called when a button is
clicked, or that a certain class should be serialized to JSON. I have experience working
with a few different annotations, including @Override, @Nullable, and @JsonProperty.

32. What is the difference between @Path and @Query parameters?


@Path parameters are used to fill in the dynamic values in a URL path, while @Query
parameters are used to specify values for query string parameters.

33. Do you think Retrofit can be used from both Kotlin and Java simultaneously in
the same project?
Yes, I think that Retrofit can be used from both Kotlin and Java simultaneously in the
same project. I don’t see any reason why it couldn’t be used in this way, as it would just
be a matter of adding the appropriate dependencies for each language.

34. What is the purpose of Callback methods in Retrofit?


The purpose of the Callback methods is to notify the caller of the Retrofit request
whether the request was successful or not. If the request was successful, then the
onResponse() method is called. If the request was not successful, then the onFailure()
method is called.

35. What is meant by synchronous and asynchronous calls in Retrofit?


A synchronous Retrofit call will execute the network request on the same thread that the
Retrofit call was made on. This means that the main UI thread will be blocked while the
network request is being executed. An asynchronous Retrofit call will instead create a
new thread to execute the network request on, meaning that the main UI thread will not
be blocked.

36. What is the difference between POST and GET requests?


The main difference between POST and GET requests is that POST requests include a
body payload that can be used to send data to the server, while GET requests do not.
This means that POST requests are typically used when you want to create or update
data on the server, while GET requests are used for fetching data from the server.

37. How can multiple headers be sent along with a request in Retrofit?
In Retrofit, you can specify multiple headers by creating a Headers class and adding
multiple @Header annotations to it.

38. What is your understanding of interceptors in Retrofit?


Interceptors are a powerful mechanism that can monitor, rewrite, and retry network
requests. In Retrofit, interceptors are used to intercept and modify HTTP requests
before they are sent to the server. This can be useful for adding common headers,
query parameters, or for logging network requests.

39. What is the significance of retrofit logging level?


The logging level is used to determine the amount of information that is logged by the
Retrofit library. By default, Retrofit will only log requests and responses at the “basic”
level, which includes information such as the request method, URL, headers, and body.
However, you can increase the logging level to “headers” or “full”, which will include
additional information such as the request and response headers and bodies.

You might also like