Integrating Retrofit for API Communication
Course: Android App Development
Topic: Integrating Retrofit for API Communication
Course Link: Android App Development Course
Demo Link: Register for a Course Demo
Website: JustAcademy
1. Introduction to Retrofit
Retrofit is a type-safe HTTP client library for Android and Java applications. It simplifies communication between Android applications and remote servers by converting API definitions into executable HTTP requests.
Modern Android applications frequently communicate with REST APIs to retrieve user profiles, display product listings, submit forms, authenticate users, and synchronize application data. Retrofit makes these operations easier by providing annotations, request parameter handling, and response conversion.
Instead of manually constructing every HTTP request and parsing every JSON response, developers can define an API interface and allow Retrofit to manage much of the networking process.
Learning Objectives
- Understand Retrofit and its role in Android development.
- Configure Retrofit dependencies in an Android project.
- Create data models and API service interfaces.
- Configure a Retrofit instance and JSON converter.
- Perform GET, POST, PUT, PATCH, and DELETE operations.
- Use Kotlin coroutines for asynchronous API communication.
- Handle HTTP errors, network failures, and JSON parsing issues.
- Organize API code using repositories and ViewModels.
- Apply security and performance best practices.
2. Why Do Android Developers Use Retrofit?
Android applications often need to exchange information with backend services. Although requests can be made using lower-level networking APIs, implementing all request construction, serialization, parsing, and error handling manually can increase code complexity.
Retrofit provides a structured approach to these tasks.
Advantages of Retrofit
- Type-safe API definitions: API operations can be represented as Kotlin or Java interface methods.
- Annotation-based configuration: Annotations such as
@GET, @POST, and @Path describe HTTP requests.
- JSON conversion: Converter libraries can transform JSON into Kotlin objects and serialize request objects.
- Coroutine support: Suspend functions provide a convenient way to make asynchronous requests.
- Request customization: Headers, query parameters, request bodies, and path variables can be configured.
- Error handling: Response objects provide access to status codes, headers, and error bodies.
- Maintainability: Networking logic can be separated from UI code.
Example Use Cases
| Application |
API Operation |
| E-commerce app |
Retrieve products and submit orders. |
| Social media app |
Load posts, comments, and user profiles. |
| Banking app |
Retrieve account information through secured services. |
| Learning app |
Load courses, lessons, and student progress. |
| Food delivery app |
Retrieve restaurants and track order status. |
3. Understanding Retrofit Architecture
Retrofit typically works with an HTTP client, a converter, and an API service interface.
Main Components
- Retrofit instance: Holds the base URL, converters, and optional client configuration.
- API service interface: Defines endpoints and HTTP methods through annotations.
- Data models: Represent request and response data.
- Converter: Converts JSON or other supported representations into application objects.
- OkHttp client: Handles HTTP transport and can provide timeouts, interceptors, and connection management.
- Repository: Provides an application-facing interface to remote data sources and coordinates data access.
- ViewModel: Manages UI-related state and invokes the repository.
Architecture Flow
Android UI
|
v
ViewModel
|
v
Repository
|
v
Retrofit API Interface
|
v
Retrofit + JSON Converter
|
v
OkHttp HTTP Client
|
v
HTTPS REST API
|
v
Server Response
|
v
Data Model / Repository
|
v
ViewModel State
|
v
Updated Android UI
This architecture separates user-interface responsibilities from networking and data-access responsibilities. It also makes API code easier to test and maintain.
4. Setting Up Retrofit in Android Studio
Before integrating Retrofit, create an Android project in Android Studio and ensure that Kotlin support is configured.
Step 1: Add Dependencies
Open the app-level build.gradle.kts file and add Retrofit, a JSON converter, and lifecycle ViewModel coroutine support if they are not already included.
dependencies {
implementation("com.squareup.retrofit2:retrofit:2.11.0")
implementation("com.squareup.retrofit2:converter-gson:2.11.0")
implementation(
"androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7"
)
}
These are example dependency versions for demonstrating setup. Check library compatibility and use versions approved for your project. You do not need an additional Retrofit coroutine adapter for standard suspend-function support in Retrofit 2.6.0 and later.
After adding dependencies, synchronize the Gradle project.
Step 2: Add Internet Permission
Open AndroidManifest.xml and declare the INTERNET permission outside the application element.
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission
android:name="android.permission.INTERNET" />
<application
android:label="RetrofitDemo"
android:theme="@style/Theme.App">
...
</application>
</manifest>
The INTERNET permission is a normal permission and generally does not require a runtime permission dialog.
Step 3: Use HTTPS
Use an HTTPS base URL for production services. Android applications targeting modern Android versions generally restrict cleartext HTTP traffic by default, unless the application configuration permits it.
Do not enable cleartext traffic globally just to bypass an insecure endpoint. Correct the endpoint or use a narrowly scoped development configuration when necessary.
5. Understanding API Documentation Before Integration
Before creating Retrofit methods, study the API documentation. It defines the correct endpoint URLs, supported HTTP methods, required headers, request fields, response structures, and error formats.
Information to Collect
- Base URL of the API.
- Endpoint paths and HTTP methods.
- Required query parameters and path variables.
- Required request headers.
- Authentication requirements.
- Request body format.
- Successful response format.
- Possible HTTP status codes and error response structures.
- Pagination and rate-limit behavior.
Example API Specification
| Property |
Example |
| Base URL |
https://api.example.com/ |
| Endpoint |
products |
| Method |
GET |
| Response type |
JSON array of products |
| Authentication |
Depends on the service |
The example hostname is illustrative. Replace it with an API that you own or are authorized to use.
6. Creating Data Models in Kotlin
Data models represent the information returned by an API or submitted in a request. Their properties should match the API's JSON structure and types.
Example JSON Response
{
"id": 101,
"name": "Wireless Mouse",
"price": 799.0,
"inStock": true
}
Corresponding Kotlin Data Class
data class Product(
val id: Int,
val name: String,
val price: Double,
val inStock: Boolean
)
With the Gson converter configured, Retrofit can deserialize a compatible JSON response into a Product object.
Why Data Models Are Useful
- They provide a structured representation of API data.
- They reduce manual JSON parsing.
- They make property access more readable.
- They improve type safety during development.
- They simplify testing and application-state management.
Handling Different JSON Property Names
Sometimes an API uses a property name that differs from the Kotlin property name. Gson's @SerializedName annotation can map the two names.
import com.google.gson.annotations.SerializedName
data class User(
val id: Int,
@SerializedName("full_name")
val fullName: String,
val email: String
)
In this example, the JSON field full_name maps to the Kotlin property fullName.
7. Creating a Retrofit API Interface
The API interface declares which endpoints the Android application can call. Retrofit reads the annotations and creates implementations of the interface.
Example ProductApi Interface
import retrofit2.http.GET
import retrofit2.http.Path
import retrofit2.http.Query
interface ProductApi {
@GET("products")
suspend fun getProducts(): List<Product>
@GET("products/{id}")
suspend fun getProductById(
@Path("id") productId: Int
): Product
@GET("products")
suspend fun getProductsByCategory(
@Query("category") category: String
): List<Product>
}
Explanation
@GET("products") defines a GET request to the products endpoint.
@Path("id") inserts a value into the endpoint path.
@Query("category") adds a URL query parameter.
suspend enables the method to be called from a Kotlin coroutine.
List<Product> represents the expected response body.
These return types assume that the API returns the expected JSON structure and a successful response. A direct body return throws an HTTP exception for unsuccessful HTTP responses. Use Response<T> when you need to inspect unsuccessful status codes explicitly.
8. Configuring the Retrofit Instance
After defining the API interface, configure Retrofit with a base URL and a converter factory.
Creating ApiClient.kt
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
object ApiClient {
private const val BASE_URL =
"https://api.example.com/"
val productApi: ProductApi by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(
GsonConverterFactory.create()
)
.build()
.create(ProductApi::class.java)
}
}
Explanation of the Code
object ApiClient creates a Kotlin singleton for shared API configuration.
BASE_URL specifies the root URL used to construct endpoint requests.
Retrofit.Builder() starts configuring Retrofit.
baseUrl() sets the API's base URL.
addConverterFactory() configures JSON serialization and deserialization through Gson.
build() creates the Retrofit instance.
create() generates an implementation of the API interface.
Retrofit base URLs must satisfy Retrofit's URL requirements, including a trailing slash. Replace the example URL with the real service address before running the application.
9. Integrating GET Requests
A GET request retrieves information from a server. It is commonly used to display lists, user profiles, product details, and other read-only information.
API Method
@GET("products")
suspend fun getProducts(): List<Product>
Calling the GET Request
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
class ProductViewModel : ViewModel() {
fun loadProducts() {
viewModelScope.launch {
try {
val products =
ApiClient.productApi.getProducts()
Log.d(
"ProductViewModel",
"Products loaded: ${products.size}"
)
// Publish products to observable UI state.
} catch (e: Exception) {
Log.e(
"ProductViewModel",
"Unable to load products",
e
)
// Publish an error state.
}
}
}
}
What Happens During the Request?
- The ViewModel starts a coroutine.
- The Retrofit method initiates the HTTP request.
- The server processes the request.
- The response body is converted into Kotlin product objects.
- The ViewModel receives the result or an exception.
- The application updates its observable UI state.
Retrofit suspend functions do not block the calling thread while waiting for the network response. Use lifecycle-aware scopes such as viewModelScope so work is cancelled when its owner is cleared.
10. Integrating POST Requests
A POST request submits information to a server. Common examples include creating a user, adding a product, submitting a form, or placing an order.
Step 1: Create a Request Model
data class CreateProductRequest(
val name: String,
val price: Double
)
Step 2: Define the API Method
import retrofit2.http.Body
import retrofit2.http.POST
interface ProductApi {
@POST("products")
suspend fun createProduct(
@Body request: CreateProductRequest
): Product
}
The @Body annotation passes the request object to the configured converter, which serializes it into JSON.
Step 3: Submit the Request
viewModelScope.launch {
try {
val request = CreateProductRequest(
name = "Wireless Mouse",
price = 799.0
)
val product =
ApiClient.productApi.createProduct(request)
// Publish the newly created product.
} catch (e: Exception) {
// Publish an appropriate error state.
}
}
The server must validate submitted data and enforce its business rules. Client-side validation improves the user experience but cannot replace server-side validation.
11. Integrating PUT, PATCH, and DELETE Requests
Retrofit supports multiple HTTP methods through annotations. The API must expose the relevant operations for these methods to work.
PUT Request
import retrofit2.http.Body
import retrofit2.http.PUT
import retrofit2.http.Path
@PUT("products/{id}")
suspend fun replaceProduct(
@Path("id") id: Int,
@Body request: CreateProductRequest
): Product
PUT generally replaces the target resource representation. Follow the backend's documented update semantics.
PATCH Request
import retrofit2.http.Body
import retrofit2.http.PATCH
import retrofit2.http.Path
data class UpdatePriceRequest(
val price: Double
)
@PATCH("products/{id}")
suspend fun updateProductPrice(
@Path("id") id: Int,
@Body request: UpdatePriceRequest
): Product
PATCH applies a partial modification. The exact body format depends on the API, which may require a custom JSON object or a standard patch document.
DELETE Request
import retrofit2.Response
import retrofit2.http.DELETE
import retrofit2.http.Path
@DELETE("products/{id}")
suspend fun deleteProduct(
@Path("id") id: Int
): Response<Unit>
Returning Response<Unit> allows the caller to inspect the status code without requiring a response body. A successful DELETE request may return 204 No Content.
Important: Never assume an operation succeeded solely because the request was sent. Inspect the HTTP response and update the UI only after handling the result.
12. Using Path Parameters and Query Parameters
Path parameters identify a particular resource, while query parameters commonly filter, sort, or paginate a collection.
Path Parameter Example
@GET("products/{id}")
suspend fun getProduct(
@Path("id") id: Int
): Product
Calling the method with ID 101 produces a request to /products/101.
Query Parameter Example
@GET("products")
suspend fun searchProducts(
@Query("category") category: String,
@Query("page") page: Int
): List<Product>
A call with category electronics and page 2 produces a request similar to /products?category=electronics&page=2.
Using Optional Query Parameters
@GET("products")
suspend fun getProducts(
@Query("category") category: String?,
@Query("page") page: Int = 1
): List<Product>
Nullable query parameters can be omitted when they are not supplied. The API's documented defaults should determine which parameters are optional.
13. Sending Headers and Authentication Tokens
Some APIs require headers that provide content information, access tokens, language preferences, or other request metadata.
Using the Header Annotation
import retrofit2.http.GET
import retrofit2.http.Header
@GET("profile")
suspend fun getProfile(
@Header("Authorization") authorization: String
): User
Calling the Method
val user = ApiClient.userApi.getProfile(
authorization = "Bearer YOUR_ACCESS_TOKEN"
)
Use a valid token supplied by the authentication system. Do not include actual credentials in shared code, screenshots, or public repositories.
Adding Common Headers Through an Interceptor
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
val client = OkHttpClient.Builder()
.addInterceptor(
Interceptor { chain ->
val request = chain.request()
.newBuilder()
.header("Accept", "application/json")
.build()
chain.proceed(request)
}
)
.build()
val retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/")
.client(client)
.addConverterFactory(
GsonConverterFactory.create()
)
.build()
This example adds a common Accept header. If an interceptor adds authorization headers, retrieve tokens securely and account for token expiration, refresh failures, and concurrent requests.
14. Handling API Responses with Response<T>
When an API method returns the response body directly, Retrofit throws an HttpException for unsuccessful HTTP status codes. If the application needs to inspect status codes and error bodies manually, return Response<T>.
Example API Method
import retrofit2.Response
import retrofit2.http.GET
@GET("products")
suspend fun getProductsResponse():
Response<List<Product>>
Checking the Response
val response =
ApiClient.productApi.getProductsResponse()
if (response.isSuccessful) {
val products = response.body().orEmpty()
// Publish successful data.
} else {
val statusCode = response.code()
val errorBody = response.errorBody()?.string()
// Map the error to a suitable application state.
}
Important Response Properties
isSuccessful indicates whether the status code is in the successful 2xx range.
code() returns the HTTP status code.
body() provides the converted successful response body, if present.
errorBody() provides the unsuccessful response body, if available.
headers() provides response headers.
Read an error body only when needed because it is a one-shot response body. Avoid logging sensitive error content. A successful response may legitimately have no body, so handle null values appropriately.
15. Handling Network Errors and Exceptions
API communication can fail because of connectivity problems, server errors, invalid responses, expired credentials, or TLS certificate issues. Error handling should distinguish these conditions and communicate useful information to the UI.
Common Error Types
| Error |
Possible Cause |
Handling Strategy |
IOException |
Connection or transport failure. |
Show a connection message and offer retry when appropriate. |
HttpException |
Unsuccessful HTTP status when a direct body return is used. |
Inspect the status code and map it to a safe message. |
| Serialization error |
Unexpected JSON structure or incompatible data types. |
Review response fields and converter configuration. |
| TLS error |
Certificate or trust configuration issue. |
Correct the certificate or trust configuration. |
| Timeout |
Network or server takes too long. |
Review timeout settings and service performance. |
| Cancellation |
Coroutine or screen work is cancelled. |
Allow cancellation to propagate correctly. |
Example Exception Handling
import java.io.IOException
import kotlinx.coroutines.CancellationException
import retrofit2.HttpException
try {
val products =
ApiClient.productApi.getProducts()
// Publish success state.
} catch (e: CancellationException) {
throw e
} catch (e: HttpException) {
val code = e.code()
// Map the HTTP status to a UI error state.
} catch (e: IOException) {
// Handle connection or transport failures.
} catch (e: Exception) {
// Handle unexpected failures.
}
Cancellation should generally be rethrown rather than treated as an ordinary error. Avoid catching exceptions without recording or handling the failure meaningfully.
16. Integrating Retrofit with a Repository
A repository separates data-access logic from the ViewModel and UI. It can provide a consistent interface for remote data, local storage, caching, and error handling.
ProductRepository.kt
class ProductRepository(
private val api: ProductApi
) {
suspend fun getProducts(): List<Product> {
return api.getProducts()
}
suspend fun getProductById(
id: Int
): Product {
return api.getProductById(id)
}
}
Why Use a Repository?
- It prevents the ViewModel from directly managing HTTP implementation details.
- It provides a central place to coordinate remote and local data.
- It improves testability by allowing dependencies to be replaced with fakes.
- It makes future caching and data-source changes easier.
- It helps keep UI logic separate from networking logic.
For a larger application, create the repository through dependency injection or a dedicated application-level dependency provider instead of manually constructing it in multiple screens.
17. Integrating Retrofit with a ViewModel
The ViewModel coordinates repository calls and exposes application state to the UI. A clear state model helps the screen respond correctly to loading, success, empty results, and errors.
Define UI State
sealed interface ProductUiState {
data object Loading : ProductUiState
data class Success(
val products: List<Product>
) : ProductUiState
data object Empty : ProductUiState
data class Error(
val message: String
) : ProductUiState
}
Implement the ViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import java.io.IOException
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import retrofit2.HttpException
class ProductViewModel(
private val repository: ProductRepository
) : ViewModel() {
private val _uiState =
MutableStateFlow<ProductUiState>(
ProductUiState.Loading
)
val uiState: StateFlow<ProductUiState> =
_uiState.asStateFlow()
fun loadProducts() {
viewModelScope.launch {
_uiState.value = ProductUiState.Loading
try {
val products = repository.getProducts()
_uiState.value =
if (products.isEmpty()) {
ProductUiState.Empty
} else {
ProductUiState.Success(products)
}
} catch (e: CancellationException) {
throw e
} catch (e: HttpException) {
_uiState.value =
ProductUiState.Error(
"The server returned an error."
)
} catch (e: IOException) {
_uiState.value =
ProductUiState.Error(
"Check your internet connection."
)
} catch (e: Exception) {
_uiState.value =
ProductUiState.Error(
"Unable to load products."
)
}
}
}
}
This example uses a generic error message to avoid exposing internal details. A production application can map known HTTP status codes to specific user-friendly messages while recording safe diagnostic information.
Displaying State in the UI
The Activity, Fragment, or Jetpack Compose screen should observe the ViewModel state and display the appropriate interface for each state.
- Loading: Show a progress indicator.
- Success: Display the product list.
- Empty: Explain that no products were found.
- Error: Show a safe message and a retry option.
18. Using Retrofit with Jetpack Compose
In Jetpack Compose, collect the ViewModel's state using lifecycle-aware state collection and render the appropriate screen.
Illustrative Compose Example
@Composable
fun ProductScreen(
viewModel: ProductViewModel
) {
val state by viewModel.uiState
.collectAsStateWithLifecycle()
when (val current = state) {
ProductUiState.Loading -> {
CircularProgressIndicator()
}
is ProductUiState.Success -> {
LazyColumn {
items(current.products) { product ->
Text(
text = "${product.name}: ${product.price}"
)
}
}
}
ProductUiState.Empty -> {
Text("No products available.")
}
is ProductUiState.Error -> {
Text(current.message)
}
}
}
This example assumes the relevant Compose, lifecycle, and UI imports are present. Trigger loadProducts() from an appropriate lifecycle-aware event, such as a ViewModel initialization or a controlled UI action, rather than restarting the request during every recomposition.
19. File Uploads and Multipart Requests
Retrofit supports multipart requests for uploading files such as profile images and documents when the backend provides a multipart endpoint.
Example API Declaration
import okhttp3.MultipartBody
import okhttp3.RequestBody
import retrofit2.http.Multipart
import retrofit2.http.POST
import retrofit2.http.Part
interface UploadApi {
@Multipart
@POST("upload")
suspend fun uploadFile(
@Part file: MultipartBody.Part,
@Part("description") description: RequestBody
): UploadResponse
}
The application must prepare the file part and description request body using the appropriate OkHttp request-body APIs. The endpoint and accepted file types depend on the backend implementation.
Upload Best Practices
- Validate file size and supported file types.
- Show upload progress when the user needs feedback.
- Handle upload cancellation and failed transfers.
- Use HTTPS and enforce server-side file validation.
- Avoid reading very large files entirely into memory.
20. API Logging and Debugging with OkHttp
During development, request and response logging can help diagnose incorrect endpoints, missing headers, and unexpected responses. OkHttp provides a logging interceptor that can be added to the project.
Example Development Configuration
val logging = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BASIC
}
val client = OkHttpClient.Builder()
.addInterceptor(logging)
.build()
This example assumes the OkHttp logging-interceptor dependency is included. BASIC logging records request and response lines, while more detailed levels may expose headers or bodies.
Debugging Checklist
- Verify the base URL and endpoint path.
- Check the HTTP method and parameter names.
- Confirm that the request body matches the API schema.
- Inspect the status code and response headers.
- Confirm the INTERNET permission is declared.
- Check connectivity, DNS resolution, and server availability.
- Verify the JSON converter and data model definitions.
- Check authentication requirements and token expiration.
- Inspect TLS errors without disabling certificate validation.
- Remove verbose logging from production configurations.
21. Security Best Practices for Retrofit
Retrofit does not automatically make an API secure. Application developers must configure the HTTP client appropriately and follow the backend's authentication and authorization requirements.
- Use HTTPS: Protect API traffic with TLS.
- Validate certificates: Never use trust-all certificate managers or permissive hostname verifiers in production.
- Protect tokens: Store sensitive credentials carefully and avoid logging them.
- Avoid embedded secrets: Do not rely on hiding private server keys inside the application package.
- Validate responses: Handle unexpected, incomplete, or malformed data.
- Use least privilege: Request only the permissions and API scopes the application requires.
- Protect personal data: Avoid unnecessary storage and transmission of sensitive information.
- Enforce server authorization: The backend must validate permissions for every protected operation.
- Configure timeouts: Prevent requests from waiting indefinitely.
- Handle redirects carefully: Ensure credentials are not forwarded to untrusted destinations.
22. Performance Optimization
Efficient API communication improves responsiveness and reduces unnecessary network activity.
Recommended Techniques
- Reuse a configured Retrofit instance instead of creating a new one for each request.
- Use an appropriate OkHttp client configuration.
- Configure connection, read, and write timeouts according to application requirements.
- Use pagination for large datasets.
- Cache data when allowed by the API and privacy requirements.
- Debounce search requests.
- Cancel obsolete requests when their results are no longer needed.
- Use exponential backoff with jitter for appropriate retryable failures.
- Avoid automatically retrying non-idempotent operations unless duplicate processing is safely prevented.
- Use background work mechanisms such as WorkManager for suitable deferrable tasks.
Example Timeout Configuration
import java.util.concurrent.TimeUnit
import okhttp3.OkHttpClient
val client = OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.build()
These values are illustrative. Choose timeouts based on the endpoint's expected response time and user experience requirements.
23. Common Retrofit Integration Mistakes
| Mistake |
Impact |
Solution |
| Missing INTERNET permission |
Network requests cannot operate as expected. |
Declare the permission in the manifest. |
| Incorrect base URL |
Requests target the wrong host or path. |
Verify the endpoint and trailing slash. |
| Incorrect JSON model |
Deserialization may fail or produce incorrect values. |
Match the API schema and use serialization annotations. |
| Missing converter |
JSON cannot be converted to the requested model. |
Register a compatible converter factory. |
| Ignoring error responses |
Users may see incorrect or empty data. |
Inspect status codes and map errors to UI states. |
| Using network calls on the main thread |
The UI may freeze. |
Use suspend functions and appropriate coroutine scopes. |
| Creating clients repeatedly |
Wastes resources and complicates configuration. |
Reuse shared Retrofit and OkHttp instances. |
| Logging authentication tokens |
Sensitive credentials may be exposed. |
Redact headers and restrict debug logging. |
| Retrying all failed POST requests |
Duplicate records or operations may occur. |
Use a documented retry policy and idempotency controls where supported. |
| Ignoring coroutine cancellation |
Unwanted work may continue or cancellation may be swallowed. |
Propagate CancellationException correctly. |
24. Practical Project: Product Listing App Using Retrofit
Project Objective
Develop an Android application that retrieves product information from a REST API and displays the results in a user-friendly list.
Functional Requirements
- Fetch products from an HTTPS endpoint.
- Convert the JSON response into Kotlin data models.
- Display product names and prices.
- Show a loading indicator while data is being fetched.
- Handle empty responses and API failures.
- Provide a retry option.
- Open a product detail screen when a product is selected.
Development Steps
- Create a Kotlin Android project.
- Add Retrofit, the JSON converter, and required coroutine-related dependencies.
- Declare the INTERNET permission.
- Create the Product data class.
- Define the ProductApi interface.
- Configure the Retrofit instance and base URL.
- Create a ProductRepository.
- Implement a ViewModel with loading, success, empty, and error states.
- Display the product list using RecyclerView or Jetpack Compose.
- Test successful responses, empty data, invalid URLs, and server errors.
- Verify HTTPS configuration and remove sensitive logging.
Expected Outcome
The application retrieves products from the backend and displays them. It presents a loading state while the request is in progress, an empty state when no products are available, and a useful error message when the request fails.
Project Extension Ideas
- Add search and category filtering.
- Implement pagination.
- Add product creation using POST.
- Implement product updates and deletion.
- Add authentication for protected endpoints.
- Cache suitable responses for offline browsing.
- Write unit tests for repository and ViewModel behavior.
25. Interview Questions and Answers
Q1. What is Retrofit?
Answer: Retrofit is a type-safe HTTP client for Android and Java that simplifies communication with REST APIs through annotated interfaces and converter libraries.
Q2. Why is Retrofit commonly used in Android development?
Answer: It reduces networking boilerplate, supports multiple HTTP methods, integrates with JSON converters, and provides coroutine and callback-based request options.
Q3. What is the purpose of the Retrofit Builder?
Answer: The builder configures the base URL, converters, HTTP client, and other networking components before creating the Retrofit instance.
Q4. What is an API service interface?
Answer: It defines API operations using methods and annotations such as @GET, @POST, @Path, and @Query.
Q5. What is the purpose of a converter factory?
Answer: It converts response bodies into Kotlin or Java objects and serializes supported request objects into the required format, such as JSON.
Q6. What is the difference between @Path and @Query?
Answer: @Path inserts a value into the URL path, while @Query adds a query parameter to the URL.
Q7. What is the purpose of @Body?
Answer: It passes an object as the request body, which the configured converter can serialize into JSON or another supported representation.
Q8. How does Retrofit work with Kotlin coroutines?
Answer: Retrofit supports suspend functions, allowing requests to be written in sequential Kotlin code without blocking the calling thread while waiting for a network response.
Q9. What is the difference between Response<T> and T?
Answer: Returning T gives the converted successful response body and throws an HTTP exception for unsuccessful responses. Returning Response<T> allows explicit inspection of status codes, headers, successful bodies, and error bodies.
Q10. What is OkHttp?
Answer: OkHttp is an HTTP client used by Retrofit for network transport. It supports connection management, interceptors, timeouts, and other HTTP features.
Q11. Why should Retrofit instances be reused?
Answer: Reusing a configured instance avoids unnecessary setup and allows the application to share client configuration and connection-management resources.
Q12. How do you handle API errors?
Answer: Inspect HTTP responses and handle transport exceptions, serialization failures, timeouts, and cancellation. Convert failures into appropriate application states.
Q13. What is the purpose of an interceptor?
Answer: An interceptor can inspect or modify requests and responses. Common uses include adding headers, logging safe diagnostics, and implementing specific networking policies.
Q14. How do you send authentication tokens with Retrofit?
Answer: Tokens can be passed using the @Header annotation or added through an appropriately configured interceptor. Tokens must be protected and transmitted over HTTPS.
Q15. Why should network requests not block the main thread?
Answer: Blocking the main thread can freeze the UI and cause application-not-responding errors. Coroutine-based suspend functions and asynchronous APIs help maintain responsiveness.
Q16. What is the repository pattern?
Answer: It separates data-access logic from the ViewModel and UI and provides a consistent interface for remote or local data sources.
Q17. What is the role of a ViewModel?
Answer: A ViewModel manages UI-related state and coordinates application operations while separating screen logic from the UI implementation.
Q18. How can Retrofit requests be tested?
Answer: Developers can use mocked API responses, fake repositories, local test servers, and integration tests to validate request construction, parsing, and error handling.
26. Practice Exercises
- Add Retrofit and a JSON converter to a new Android project.
- Create a Kotlin data class matching a sample JSON response.
- Define a GET endpoint that retrieves a collection of users.
- Implement a GET endpoint using a path parameter.
- Create a POST request that submits a JSON object.
- Implement a PATCH request for a partial update.
- Handle unsuccessful HTTP responses using
Response<T>.
- Create a repository and ViewModel for loading products.
- Display loading, success, empty, and error states in the UI.
- Test connection failures, invalid JSON, and HTTP error responses.
- Configure an OkHttp interceptor for a common request header.
- Review the application for insecure traffic and accidental credential logging.
27. Quick Revision Notes
- Retrofit: Type-safe HTTP client for Android and Java.
- Retrofit Builder: Configures the base URL, converters, and HTTP client.
- API interface: Declares HTTP endpoints using annotations.
- Data model: Represents request or response data.
- Converter: Serializes and deserializes supported data formats.
- OkHttp: HTTP client used for network transport.
- @GET: Retrieves data.
- @POST: Submits data or initiates an operation.
- @PUT: Replaces a resource representation.
- @PATCH: Applies a partial update.
- @DELETE: Requests resource deletion.
- @Path: Supplies a URL path value.
- @Query: Adds a query parameter.
- @Body: Supplies a request body.
- @Header: Supplies a request header.
- Suspend function: Supports asynchronous coroutine-based calls.
- Repository: Separates data-access logic from UI logic.
- ViewModel: Manages UI-related state.
- Interceptor: Inspects or modifies HTTP requests and responses.
- HTTPS: Protects network communication through TLS.
28. Conclusion
Integrating Retrofit is an important skill in Android development because modern applications frequently depend on backend services. Retrofit simplifies API communication by providing annotated service interfaces, request parameter handling, JSON conversion, and integration with Kotlin coroutines.
By combining Retrofit with OkHttp, repositories, ViewModels, and lifecycle-aware UI state, developers can build applications that are responsive, testable, secure, and maintainable.
Successful integration requires more than making a request. Developers should understand the API contract, validate responses, handle errors correctly, protect authentication credentials, and test both successful and unsuccessful scenarios.
Additional Learning Resources
Enroll in Android App Development Training
Explore Android development concepts, networking, REST API integration, and practical application development through JustAcademy.
Course Link: Android App Development Course
Demo Registration: Register for a Course Demo
JustAcademy Homepage: Visit JustAcademy