
Retrofit Android Example Tutorial
Retrofit is a type-safe HTTP client library for Android developed by Square that simplifies the process of making network requests to REST APIs. Instead of dealing with low-level networking code, Retrofit converts your HTTP API into a Java interface, making API calls feel as natural as calling regular methods. This tutorial will walk you through implementing Retrofit in your Android project, from basic setup to advanced configurations, including handling different response types, error management, and performance optimization techniques that work well across various hosting environments including VPS deployments.
How Retrofit Works Under the Hood
Retrofit uses annotation processing and reflection to generate implementation code for your API interfaces at runtime. When you define an interface with HTTP annotations, Retrofit creates a concrete implementation that handles the actual network requests using OkHttp as the underlying HTTP client.
The library follows a three-layer architecture:
- Interface Layer: Where you define your API endpoints using annotations
- Retrofit Instance: Configures base URL, converters, and interceptors
- OkHttp Client: Handles the actual HTTP communication
Here’s how a typical request flows through Retrofit:
API Interface Method Call → Retrofit Proxy → Request Building →
OkHttp Execution → Response Processing → Callback/Return
Step-by-Step Implementation Guide
Adding Dependencies
First, add Retrofit dependencies to your app-level build.gradle
file:
dependencies {
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.10.0'
}
Don’t forget to add internet permission in your AndroidManifest.xml
:
<uses-permission android:name="android.permission.INTERNET" />
Creating Data Models
Define your data classes that match your API response structure:
public class User {
@SerializedName("id")
private int id;
@SerializedName("name")
private String name;
@SerializedName("email")
private String email;
// Constructors, getters, and setters
public User(String name, String email) {
this.name = name;
this.email = email;
}
public int getId() { return id; }
public String getName() { return name; }
public String getEmail() { return email; }
}
Creating the API Interface
Define your API endpoints using Retrofit annotations:
import retrofit2.Call;
import retrofit2.http.*;
import java.util.List;
public interface ApiService {
@GET("users")
Call<List<User>> getUsers();
@GET("users/{id}")
Call<User> getUser(@Path("id") int userId);
@POST("users")
Call<User> createUser(@Body User user);
@PUT("users/{id}")
Call<User> updateUser(@Path("id") int userId, @Body User user);
@DELETE("users/{id}")
Call<Void> deleteUser(@Path("id") int userId);
@GET("users")
Call<List<User>> getUsersWithQuery(@Query("page") int page,
@Query("limit") int limit);
}
Setting Up Retrofit Instance
Create a Retrofit configuration class:
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import java.util.concurrent.TimeUnit;
public class RetrofitClient {
private static final String BASE_URL = "https://jsonplaceholder.typicode.com/";
private static Retrofit retrofit;
public static Retrofit getRetrofitInstance() {
if (retrofit == null) {
// Create logging interceptor
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
// Configure OkHttp client
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(logging)
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.build();
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
public static ApiService getApiService() {
return getRetrofitInstance().create(ApiService.class);
}
}
Making API Calls
Here’s how to implement API calls in your Activity or Fragment:
public class MainActivity extends AppCompatActivity {
private ApiService apiService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
apiService = RetrofitClient.getApiService();
// Fetch users
fetchUsers();
// Create new user
createNewUser();
}
private void fetchUsers() {
Call<List<User>> call = apiService.getUsers();
call.enqueue(new Callback<List<User>>() {
@Override
public void onResponse(Call<List<User>> call, Response<List<User>> response) {
if (response.isSuccessful() && response.body() != null) {
List<User> users = response.body();
// Update UI with users data
updateUsersList(users);
} else {
Log.e("API Error", "Response not successful: " + response.code());
}
}
@Override
public void onFailure(Call<List<User>> call, Throwable t) {
Log.e("Network Error", "Failed to fetch users: " + t.getMessage());
}
});
}
private void createNewUser() {
User newUser = new User("John Doe", "john@example.com");
Call<User> call = apiService.createUser(newUser);
call.enqueue(new Callback<User>() {
@Override
public void onResponse(Call<User> call, Response<User> response) {
if (response.isSuccessful()) {
User createdUser = response.body();
Log.d("Success", "User created with ID: " + createdUser.getId());
}
}
@Override
public void onFailure(Call<User> call, Throwable t) {
Log.e("Error", "Failed to create user: " + t.getMessage());
}
});
}
}
Real-World Examples and Use Cases
Authentication with Headers
For APIs requiring authentication, you can add headers dynamically:
public interface ApiService {
@GET("profile")
Call<User> getUserProfile(@Header("Authorization") String token);
@Headers("Content-Type: application/json")
@POST("login")
Call<LoginResponse> login(@Body LoginRequest request);
}
Or create an interceptor for automatic header injection:
public class AuthInterceptor implements Interceptor {
private String authToken;
public AuthInterceptor(String token) {
this.authToken = token;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
Request.Builder requestBuilder = original.newBuilder()
.header("Authorization", "Bearer " + authToken)
.header("Content-Type", "application/json");
Request request = requestBuilder.build();
return chain.proceed(request);
}
}
File Upload Implementation
@Multipart
@POST("upload")
Call<UploadResponse> uploadFile(@Part("description") RequestBody description,
@Part MultipartBody.Part file);
// Usage
private void uploadFile(Uri fileUri) {
File file = new File(getRealPathFromURI(fileUri));
RequestBody requestFile = RequestBody.create(MediaType.parse("image/*"), file);
MultipartBody.Part body = MultipartBody.Part.createFormData("file", file.getName(), requestFile);
RequestBody description = RequestBody.create(MediaType.parse("text/plain"), "File description");
Call<UploadResponse> call = apiService.uploadFile(description, body);
call.enqueue(new Callback<UploadResponse>() {
@Override
public void onResponse(Call<UploadResponse> call, Response<UploadResponse> response) {
if (response.isSuccessful()) {
Log.d("Upload", "File uploaded successfully");
}
}
@Override
public void onFailure(Call<UploadResponse> call, Throwable t) {
Log.e("Upload", "Upload failed: " + t.getMessage());
}
});
}
Comparison with Alternative HTTP Libraries
Feature | Retrofit | Volley | OkHttp | HttpURLConnection |
---|---|---|---|---|
Learning Curve | Medium | Easy | Hard | Hard |
Type Safety | Excellent | Poor | Good | Poor |
JSON Parsing | Automatic | Manual | Manual | Manual |
Request/Response Interception | Excellent | Limited | Excellent | Manual |
Caching | Via OkHttp | Built-in | Built-in | Manual |
File Upload | Easy | Complex | Medium | Complex |
Library Size | ~400KB | ~300KB | ~800KB | 0KB (Built-in) |
Best Practices and Common Pitfalls
Performance Optimization
Configure connection pooling and timeouts appropriately:
OkHttpClient client = new OkHttpClient.Builder()
.connectionPool(new ConnectionPool(5, 5, TimeUnit.MINUTES))
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.build();
Error Handling Best Practices
Create a centralized error handler:
public class ApiErrorHandler {
public static void handleError(Response<?> response, Throwable throwable) {
if (throwable != null) {
// Network errors
if (throwable instanceof IOException) {
Log.e("Network", "Network error: " + throwable.getMessage());
} else {
Log.e("Unexpected", "Unexpected error: " + throwable.getMessage());
}
} else if (response != null && !response.isSuccessful()) {
// HTTP errors
switch (response.code()) {
case 400:
Log.e("API", "Bad Request");
break;
case 401:
Log.e("API", "Unauthorized");
break;
case 403:
Log.e("API", "Forbidden");
break;
case 404:
Log.e("API", "Not Found");
break;
case 500:
Log.e("API", "Internal Server Error");
break;
default:
Log.e("API", "HTTP error: " + response.code());
}
}
}
}
Memory Management
Always cancel ongoing requests in lifecycle methods:
public class UserRepository {
private List<Call> activeCalls = new ArrayList<>();
public void fetchUsers(Callback<List<User>> callback) {
Call<List<User>> call = apiService.getUsers();
activeCalls.add(call);
call.enqueue(callback);
}
public void cancelAllRequests() {
for (Call call : activeCalls) {
if (!call.isCanceled()) {
call.cancel();
}
}
activeCalls.clear();
}
}
Common Pitfalls to Avoid
- Network calls on main thread: Always use
enqueue()
instead ofexecute()
- Memory leaks: Cancel requests in
onDestroy()
or use WeakReference for callbacks - Ignoring HTTP status codes: Always check
response.isSuccessful()
- Not handling null responses: Check for
response.body() != null
- Hardcoded URLs: Use BuildConfig or configuration files for different environments
Advanced Configuration and Security
SSL Certificate Pinning
For enhanced security, especially when deploying to dedicated server environments:
CertificatePinner certificatePinner = new CertificatePinner.Builder()
.add("api.example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.build();
OkHttpClient client = new OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build();
Custom Gson Configuration
Gson gson = new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.setDateFormat("yyyy-MM-dd HH:mm:ss")
.excludeFieldsWithoutExposeAnnotation()
.create();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
Request/Response Caching
File httpCacheDirectory = new File(context.getCacheDir(), "http-cache");
int cacheSize = 10 * 1024 * 1024; // 10 MB
Cache cache = new Cache(httpCacheDirectory, cacheSize);
OkHttpClient client = new OkHttpClient.Builder()
.cache(cache)
.addInterceptor(new CacheInterceptor())
.build();
This comprehensive Retrofit implementation provides a robust foundation for Android networking. The library’s integration with OkHttp ensures excellent performance and reliability, whether you’re connecting to APIs hosted on cloud platforms, VPS instances, or dedicated servers. For additional resources and detailed API documentation, check the official Retrofit documentation and GitHub repository.

This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.
This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.