BLOG POSTS
Retrofit Android Example Tutorial

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 of execute()
  • 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.

Leave a reply

Your email address will not be published. Required fields are marked