Professional Documents
Culture Documents
Android Netwroking
Android Netwroking
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.
@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();
}
};
}
}
● 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).
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.
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.
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
@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 {
interface ApiHelper {
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 {
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:
init {
fetchUsers()
}
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'
interface ApiService {
@GET("users")
suspend fun getUsers(): List<ApiUser>
@GET("more-users")
suspend fun getMoreUsers(): List<ApiUser>
@GET("error")
suspend fun getUsersWithError(): List<ApiUser>
object RetrofitBuilder {
interface ApiHelper {
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()
}
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>>
builder.client(httpClient.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.
● 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.
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 {
/*
* 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.
compile ‘com.squareup.retrofit2:retrofit:2.1.0’
-keepattributes Signature
-keepattributes Exceptions
-keep class com.squareup.okhttp3.** { *; }
-keep interface com.squareup.okhttp3.** { *; }
-dontwarn com.squareup.okhttp3.**
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.
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.
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.
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.
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.
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.