Sending GET and POST Requests from Android Applications
Course: Android App Development
Topic: Sending GET and POST Requests from Android Applications
Course Link: Android App Development Course
Demo Link: Register for a Course Demo
Website: JustAcademy
1. Introduction to HTTP Requests in Android
Modern Android applications frequently communicate with remote servers to retrieve information, submit forms, authenticate users, upload content, and synchronize data. This communication commonly takes place through HTTP or HTTPS requests to a web service or REST API.
For example, a shopping application might send a GET request to retrieve products and a POST request to submit a new order. A student management application might retrieve student records from a server and send registration details to create a new student record.
Android developers can use networking libraries such as Retrofit, OkHttp, or Ktor to simplify these operations. Retrofit is especially useful because it allows developers to define API endpoints using Kotlin interfaces and annotations.
2. Learning Objectives
- Understand HTTP, REST APIs, endpoints, requests, and responses.
- Learn the differences between GET and POST requests.
- Configure internet permissions in an Android application.
- Set up Retrofit and a JSON converter.
- Send GET requests with path and query parameters.
- Send POST requests containing JSON data.
- Parse API responses into Kotlin data classes.
- Use coroutines, repositories, and ViewModels for network operations.
- Handle HTTP errors, network failures, and loading states.
- Apply security and performance best practices.
3. Understanding HTTP and REST APIs
HTTP stands for Hypertext Transfer Protocol. It defines how clients, such as Android applications, communicate with servers. HTTPS is HTTP protected by TLS encryption and should be used for network communication involving application data.
A REST API exposes resources through URLs and uses HTTP methods to describe the requested operation. An endpoint is a specific URL path that the application accesses to work with a resource.
Example API Endpoints
| Endpoint |
Method |
Purpose |
/products |
GET |
Retrieve a collection of products. |
/products/10 |
GET |
Retrieve a particular product. |
/products |
POST |
Create a product, if supported by the API. |
/users/login |
POST |
Submit login credentials to an authentication endpoint. |
/orders |
POST |
Submit a new order. |
These endpoints are illustrative. The actual HTTP method, request fields, and response structure depend on the backend API.
4. What Is a GET Request?
A GET request retrieves a representation of a resource from a server. It is commonly used to fetch lists, product details, user profiles, search results, and dashboard information.
Characteristics of GET Requests
- Used primarily for retrieving data.
- Parameters are often passed through the URL path or query string.
- GET requests should not be used to perform operations that change server state.
- Responses may be cached according to HTTP and application caching rules.
- Query parameters may appear in browser history, proxy logs, and server logs, so sensitive values should not be placed in URLs.
Example
GET https://api.example.com/products?category=mobile&limit=20
This request asks the server for products in the mobile category, with a requested limit of 20. The server determines how the parameters are interpreted and what data is returned.
5. What Is a POST Request?
A POST request submits data to a server for processing. It is often used to create a resource, submit a form, register a user, send feedback, or place an order.
Characteristics of POST Requests
- Data is commonly sent in the request body.
- JSON is a common format for sending structured application data.
- The server validates the submitted data before processing it.
- The response may contain the created resource, a status message, or validation errors.
- POST is not automatically idempotent; repeating a request may create duplicate operations unless the API provides suitable safeguards.
Example
POST https://api.example.com/products
Content-Type: application/json
{
"name": "Wireless Headphones",
"price": 2499.0
}
The JSON body contains the product information that the server is asked to process. Whether a product is created depends on the API's contract and validation rules.
6. Difference Between GET and POST
| Feature |
GET |
POST |
| Main purpose |
Retrieve data |
Submit data for processing |
| Typical data location |
Path and query parameters |
Request body |
| Common use |
Search, list, and detail screens |
Registration, form submission, resource creation |
| Request body |
Generally not used for ordinary GET requests |
Commonly contains JSON or form data |
| Caching |
May be cacheable |
Not generally cached by default in the same way as GET |
| Idempotency |
Defined as a safe, idempotent method by HTTP semantics |
Not generally idempotent |
| Security |
HTTPS is essential when transmitting sensitive data |
HTTPS is essential when transmitting sensitive data |
POST does not make data secure simply because the data is in the body. Use HTTPS, appropriate authentication, server-side validation, and safe logging practices for both methods.
7. Adding Internet Permission
Android applications must declare internet permission to make network requests. Open AndroidManifest.xml and add the permission outside the application element.
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission
android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:label="Network Demo"
android:theme="@style/Theme.App">
<!-- Activities and other components -->
</application>
</manifest>
The INTERNET permission is a normal permission and does not require a runtime permission dialog. You may also declare ACCESS_NETWORK_STATE if your application needs to inspect connectivity state, but checking connectivity does not guarantee that a particular server is reachable.
8. Setting Up Retrofit
Retrofit is a type-safe HTTP client for Android and the JVM. It converts annotated Kotlin or Java interface methods into HTTP requests and can use converter libraries to serialize request objects and deserialize response bodies.
For this example, use Retrofit with a JSON converter such as Gson. Choose compatible stable versions for the project.
dependencies {
implementation("com.squareup.retrofit2:retrofit:<retrofit-version>")
implementation("com.squareup.retrofit2:converter-gson:<retrofit-version>")
implementation("com.squareup.okhttp3:logging-interceptor:<okhttp-version>")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:<coroutines-version>")
}
Replace each placeholder with a compatible stable version and synchronize Gradle. The logging interceptor is optional and should be configured carefully so sensitive information is not recorded.
9. Creating Kotlin Data Classes for JSON
JSON is commonly used to exchange structured information between Android applications and backend services. A converter maps JSON fields to Kotlin properties and maps request objects back to JSON.
For a product API, define a response model:
data class Product(
val id: Int,
val name: String,
val price: Double
)
Define a separate request model for creating a product:
data class CreateProductRequest(
val name: String,
val price: Double
)
Separating request and response models helps avoid sending server-managed fields such as an automatically generated ID. Ensure the property names and data types match the API's JSON schema. If the API uses different field names, configure field mappings or rename the Kotlin properties appropriately.
10. Creating a Retrofit API Interface
Retrofit uses annotations such as @GET, @POST, @Path, @Query, and @Body to define requests.
10.1 GET Request for a Product List
interface ProductApiService {
@GET("products")
suspend fun getProducts(): List<Product>
}
The endpoint path is appended to the configured base URL. The suspend function allows the request to be called from a coroutine. Retrofit handles the HTTP request without requiring the coroutine to block the main thread.
10.2 GET Request with a Path Parameter
interface ProductApiService {
@GET("products/{id}")
suspend fun getProduct(
@Path("id") productId: Int
): Product
}
If productId is 10, Retrofit substitutes it into the path to request the corresponding product. The server must expose this endpoint and return a compatible JSON response.
10.3 GET Request with Query Parameters
interface ProductApiService {
@GET("products")
suspend fun searchProducts(
@Query("category") category: String,
@Query("limit") limit: Int
): List<Product>
}
Calling searchProducts("mobile", 20) sends the category and limit as query parameters. Retrofit handles URL encoding for query values.
10.4 POST Request with a JSON Body
interface ProductApiService {
@POST("products")
suspend fun createProduct(
@Body request: CreateProductRequest
): Product
}
The Gson converter serializes the request object into JSON and converts a successful JSON response into a Product object. This example assumes the server returns the created product. If the API returns a different response structure or no response body, adjust the return type accordingly.
11. Configuring the Retrofit Instance
Create a single reusable Retrofit instance rather than rebuilding the client for every request.
object RetrofitClient {
private const val BASE_URL =
"https://api.example.com/"
val apiService: ProductApiService by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ProductApiService::class.java)
}
}
Replace https://api.example.com/ with the real API base URL. Retrofit requires the base URL to end with a slash. The example assumes the API uses JSON that can be handled by Gson.
Required imports include retrofit2.Retrofit and retrofit2.converter.gson.GsonConverterFactory.
12. Making a GET Request from a Repository
A repository separates network operations from UI components. It provides a clean interface to the rest of the application and makes it easier to change the underlying data source later.
class ProductRepository(
private val api: ProductApiService
) {
suspend fun fetchProducts(): List<Product> {
return api.getProducts()
}
suspend fun fetchProduct(
productId: Int
): Product {
return api.getProduct(productId)
}
suspend fun searchProducts(
category: String,
limit: Int
): List<Product> {
return api.searchProducts(category, limit)
}
}
The repository delegates the actual network request to Retrofit. More advanced implementations can add caching, local database access, result mapping, and application-specific error handling.
13. Making a POST Request from a Repository
A repository can also expose a function that submits data to the server.
class ProductRepository(
private val api: ProductApiService
) {
suspend fun createProduct(
name: String,
price: Double
): Product {
val request = CreateProductRequest(
name = name,
price = price
)
return api.createProduct(request)
}
}
This snippet shows the POST operation in isolation. If combining it with the previous repository example, keep all functions inside one ProductRepository class rather than declaring duplicate classes.
14. Handling Network Requests with a ViewModel
Network calls should not run directly on the main thread. With Retrofit suspend functions, coroutines can perform network operations without blocking UI interactions. A ViewModel can coordinate the request and expose loading, success, and error states.
sealed interface ProductUiState {
data object Loading : ProductUiState
data class Success(
val products: List<Product>
) : ProductUiState
data class Error(
val message: String
) : ProductUiState
}
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.fetchProducts()
_uiState.value =
ProductUiState.Success(products)
} catch (exception: CancellationException) {
throw exception
} catch (exception: Exception) {
_uiState.value = ProductUiState.Error(
exception.message ?: "Unable to load products"
)
}
}
}
}
This example demonstrates GET request state management. It assumes that ProductRepository is supplied through a ViewModel factory or dependency-injection framework. Import the relevant lifecycle and coroutine classes, including ViewModel, viewModelScope, MutableStateFlow, StateFlow, and CancellationException.
For a POST operation, expose a separate submission state so the interface can show progress, display success, and present errors without confusing form submission with list loading.
15. Example of Calling a POST Request from a ViewModel
The following example demonstrates the core submission flow. It prevents a second submission while the first one is in progress.
sealed interface SubmitUiState {
data object Idle : SubmitUiState
data object Loading : SubmitUiState
data class Success(
val product: Product
) : SubmitUiState
data class Error(
val message: String
) : SubmitUiState
}
class CreateProductViewModel(
private val repository: ProductRepository
) : ViewModel() {
private val _submitState =
MutableStateFlow<SubmitUiState>(
SubmitUiState.Idle
)
val submitState: StateFlow<SubmitUiState> =
_submitState.asStateFlow()
fun submitProduct(name: String, price: Double) {
if (_submitState.value is SubmitUiState.Loading) return
viewModelScope.launch {
_submitState.value = SubmitUiState.Loading
try {
val product = repository.createProduct(
name = name,
price = price
)
_submitState.value =
SubmitUiState.Success(product)
} catch (exception: CancellationException) {
throw exception
} catch (exception: Exception) {
_submitState.value = SubmitUiState.Error(
exception.message ?: "Submission failed"
)
}
}
}
}
Validate the product name and price before calling the ViewModel. A server may still reject invalid data, so the application should display appropriate validation errors returned by the API.
16. Reading the Response and HTTP Status Codes
When a request is successful, the server returns a response containing a status code and, often, a response body. Retrofit can return the body directly or return a Response object when the application needs access to the status code and headers.
Common HTTP Status Codes
| Status code |
Meaning |
Typical handling |
| 200 OK |
The request succeeded. |
Display or process the response. |
| 201 Created |
A resource was created successfully. |
Display success and use the returned resource if available. |
| 204 No Content |
The request succeeded without a response body. |
Do not attempt to parse a required body. |
| 400 Bad Request |
The server rejected the request format or data. |
Inspect validation details and correct the input. |
| 401 Unauthorized |
Authentication is missing or invalid. |
Refresh credentials or ask the user to sign in again. |
| 403 Forbidden |
The server refuses the operation. |
Check permissions or access rights. |
| 404 Not Found |
The requested resource or endpoint does not exist. |
Verify the URL and resource identifier. |
| 429 Too Many Requests |
The server is rate-limiting requests. |
Respect rate limits and retry guidance. |
| 500 Internal Server Error |
The server encountered an error. |
Show a useful message and retry when appropriate. |
17. Handling HTTP Errors with Retrofit Response
If the app needs to inspect HTTP status codes and error bodies, use Retrofit's Response wrapper.
interface ProductApiService {
@GET("products")
suspend fun getProductsResponse():
Response<List<Product>>
}
Process the result in a repository:
suspend fun fetchProductsSafely(): List<Product> {
val response = api.getProductsResponse()
if (response.isSuccessful) {
return response.body()
?: throw IOException("Response body was empty")
}
throw IOException(
"Request failed with HTTP ${response.code()}"
)
}
This example assumes the repository has access to api and imports retrofit2.Response and java.io.IOException. For a production application, map HTTP errors into meaningful domain errors and parse the API's documented error format when available.
When a Retrofit suspend function returns the response body directly, unsuccessful HTTP responses generally result in an HttpException. When it returns Response<T>, the application can inspect isSuccessful, code(), and errorBody().
18. GET Requests with Headers and Authentication
Some endpoints require authentication or other headers. Retrofit supports headers through annotations, while an OkHttp interceptor can add common headers to requests.
interface ProfileApiService {
@GET("profile")
suspend fun getProfile(
@Header("Authorization") authorization: String
): UserProfile
}
A call might pass a properly formatted bearer token:
val profile = api.getProfile("Bearer $accessToken")
Use secure token storage and avoid printing tokens in logs. The token must be obtained through the application's authentication flow; do not hard-code real credentials into source code.
19. POST Requests with Form Data and Other Payloads
JSON is not the only format supported by POST requests. Some APIs require form URL-encoded fields, multipart file uploads, or another documented content type.
Form URL-Encoded Data
interface RegistrationApiService {
@FormUrlEncoded
@POST("register")
suspend fun registerUser(
@Field("name") name: String,
@Field("email") email: String
): RegistrationResponse
}
Use @FormUrlEncoded and @Field when the API expects form URL-encoded data. Do not use this format if the backend expects a JSON object.
Multipart File Upload
interface UploadApiService {
@Multipart
@POST("upload")
suspend fun uploadFile(
@Part file: MultipartBody.Part
): UploadResponse
}
Multipart uploads require a correctly constructed MultipartBody.Part and a backend endpoint that accepts multipart content. Do not send large file contents as ordinary JSON strings unless the API specifically requires that format.
20. Common Errors and Troubleshooting
| Problem |
Possible cause |
Solution |
| UnknownHostException |
DNS failure, unavailable network, or an incorrect hostname. |
Verify the API URL, DNS, and network availability. |
| SocketTimeoutException |
The server did not respond within the configured timeout. |
Check server performance and configure appropriate timeouts. |
| Cleartext HTTP traffic not permitted |
The app is attempting unencrypted HTTP traffic under Android's network security rules. |
Use HTTPS whenever possible. |
| HTTP 401 |
Authentication credentials are absent, expired, or invalid. |
Check the authorization header and refresh-token flow. |
| HTTP 404 |
The endpoint path or resource ID is incorrect. |
Check the base URL, endpoint, and path parameters. |
| JSON parsing error |
Response fields or data types do not match the model. |
Inspect the actual response and update the data class or converter configuration. |
| POST returns HTTP 400 |
Required fields are missing or the payload format is incorrect. |
Check the API schema, request body, and validation rules. |
| NetworkOnMainThreadException |
A blocking network operation is executed on the UI thread. |
Use Retrofit suspend functions or perform blocking work on a background thread. |
| Duplicate records after POST |
The user submitted the same request more than once. |
Disable repeated submission while loading and use server-supported idempotency where appropriate. |
| Empty response body causes a crash |
The application assumes every successful response contains JSON data. |
Handle empty bodies and use an appropriate response type. |
21. Security Best Practices
- Use HTTPS for network communication.
- Never hard-code production passwords, API secrets, or private access tokens in the application.
- Validate user input locally for usability and validate it again on the server for security.
- Do not log passwords, authentication tokens, personal information, or sensitive request bodies.
- Use authentication and authorization appropriate to the API.
- Do not assume that POST requests are automatically secure or private.
- Use Android Network Security Configuration only when special trust requirements exist, and avoid enabling cleartext traffic unnecessarily.
- Handle authentication expiration without creating endless retry loops.
- Use server-supported idempotency mechanisms for operations that must not be duplicated.
- Return helpful user-facing errors without exposing internal server details.
22. Performance and Architecture Best Practices
- Keep networking code out of Activity and Fragment classes.
- Use a repository to isolate data access and a ViewModel to manage UI state.
- Reuse Retrofit and OkHttp client instances.
- Use Kotlin suspend functions for coroutine-based requests.
- Keep loading indicators and errors visible to users when appropriate.
- Cancel obsolete work when screens or search queries change.
- Use pagination for large API responses instead of loading all records at once.
- Consider caching responses or storing data in Room when the use case requires it.
- Use connection and read timeouts suitable for the application.
- Test success, failure, empty response, invalid input, and poor connectivity scenarios.
23. Practical Exercise
Project: Build a Product Management App that retrieves and submits product information.
- Create a Product data class with ID, name, and price.
- Configure internet permission and add Retrofit with a JSON converter.
- Create a GET endpoint that retrieves a product list.
- Display the retrieved products in a RecyclerView.
- Create a form that accepts a product name and price.
- Validate the form fields before submitting the data.
- Use a POST endpoint to send the product information as JSON.
- Show loading, success, and error states during submission.
- Display server validation messages when available.
- Test the app with valid data, invalid data, an unavailable network, and an unsuccessful HTTP response.
Expected result: The application can retrieve data from a remote API using GET and submit new information using POST while handling loading states, errors, and JSON conversion correctly.
24. Interview Questions and Answers
Q1. What is an HTTP request?
An HTTP request is a message sent by a client to a server to retrieve information or perform an operation on a resource.
Q2. What is the difference between GET and POST?
GET is primarily used to retrieve a resource, while POST submits data for processing, often to create a resource or submit a form.
Q3. What is Retrofit?
Retrofit is a type-safe HTTP client that converts annotated service interface methods into network requests and supports response converters.
Q4. What is a REST API?
A REST API exposes resources through endpoints and uses standard HTTP methods and representations to interact with those resources.
Q5. What is the purpose of the INTERNET permission?
It allows the application to open network sockets and perform internet communication. It does not require a runtime permission dialog.
Q6. What is the purpose of the @GET annotation?
It declares that a Retrofit service method performs an HTTP GET request against a specified endpoint.
Q7. What is the purpose of @POST and @Body?
@POST declares a POST request, while @Body marks an object to be serialized into the request body using the configured converter.
Q8. Why use a JSON converter?
A JSON converter transforms JSON responses into Kotlin objects and serializes request objects into JSON.
Q9. Why should network calls not block the main thread?
Blocking the main thread can make the interface unresponsive and may cause application-not-responding errors. Coroutine-based networking helps keep UI interactions responsive.
Q10. What is the purpose of a repository?
A repository separates data access from UI components and provides a consistent interface to network services, databases, or caches.
Q11. What is the difference between a network error and an HTTP error?
A network error occurs when the request cannot complete normally, such as a DNS failure. An HTTP error means the server returned an HTTP response indicating an unsuccessful request, such as 404 or 500.
Q12. Is POST always safe for sending sensitive data?
No. Use HTTPS, secure authentication, appropriate authorization, careful logging, and server-side validation. POST alone does not provide confidentiality.
25. Quick Revision Checklist
- GET: Retrieves data from an API.
- POST: Submits data for processing.
- Endpoint: The API URL path being accessed.
- JSON converter: Converts between JSON and Kotlin objects.
- Retrofit: Defines API requests using annotated interfaces.
- @Path: Supplies a value for a URL path segment.
- @Query: Supplies a URL query parameter.
- @Body: Supplies the request body object.
- Repository: Encapsulates data access.
- ViewModel: Manages screen-related state.
- Coroutines: Support asynchronous request handling without blocking the UI.
- Error handling: Accounts for network failures, unsuccessful HTTP responses, and invalid data.
- Security: Use HTTPS and never expose secrets in source code or logs.
26. Official Learning Resources
Conclusion
Sending GET and POST requests is a fundamental skill in Android development. GET requests retrieve server data, while POST requests submit information for processing. Retrofit simplifies both operations through annotated interfaces, Kotlin data classes, converters, and coroutine support. By combining Retrofit with repositories, ViewModels, proper error handling, and secure communication, developers can build reliable Android applications that interact effectively with remote APIs.
Learn more through the JustAcademy Android App Development course, register for a course demo, or visit the JustAcademy homepage.