Retrofit, a Simple HTTP Client for Android and Java

Share this article

This article was updated on 11th January, 2017. Specifically: Update for Retrofit v2.1

Exchanging data between a mobile app and a backend server is an essential need for many development projects. Whilst the task became simpler with Google’s Volley library, it has a steep learning curve and Retrofit aims to make the task even simpler.

In this tutorial, I will show you how to exchange data between an Android app and a backend PHP application using the retrofit library. This sample application will simulate a login process by sending two strings (username and password) to the server, waiting for a response, and then show it to the user.

You can find the final code for this application on GitHub.

Key Takeaways

  • Retrofit simplifies data exchange between Android apps and backend servers by providing a straightforward HTTP client that’s easier to use compared to Google’s Volley.
  • The setup in Android Studio involves adding internet permissions, Retrofit and other dependencies, and configuring a simple login UI with username and password inputs.
  • Retrofit utilizes interfaces to define HTTP requests using annotations, which helps in creating type-safe client-side APIs that reduce runtime errors.
  • The tutorial demonstrates both POST and GET requests using Retrofit, showing detailed code examples on how to handle server responses and errors effectively.
  • Server-side setup is briefly covered with a PHP script example that handles both GET and POST requests, returning JSON responses based on the provided credentials.

Building Android App

Create a new project in Android Studio, choosing a minimum API level of 18 and adding a Empty Activity.

Add a permission to access the internet to AndroidManifest.xml inside the application tag:

<uses-permission android:name="android.permission.INTERNET"/>

Add the library dependencies to the dependencies section of the build.gradle (Module: app) file:

    compile 'com.squareup.okhttp3:logging-interceptor:3.4.0'
    compile 'com.squareup.retrofit2:retrofit:2.1.0'
    compile 'com.squareup.retrofit2:converter-gson:2.1.0'
    compile 'com.squareup:otto:1.3.8'
    compile 'com.google.code.gson:gson:2.6.2'

Creating login layout

The only layout needed for this application is activity_main.xml, change it to the below:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.theodhorpandeli.retrofit.MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:id="@+id/loginLayout">    

            <EditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:id="@+id/usernameInput"
                android:hint="Username:"/>          

            <EditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:id="@+id/passwordInput"
                android:hint="Password:"/>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/loginButtonPost"
                android:text="Login - Post"
                android:layout_gravity="right"
                android:layout_weight="1" />

            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/loginButtonGet"
                android:text="Login - Get"
                android:layout_weight="1" />

        </LinearLayout>

    </LinearLayout>

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="fill_parent"
        android:layout_below="@id/loginLayout">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/information"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="20dp" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/extraInformation"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="20dp" />
    </LinearLayout>

</RelativeLayout>

The layout includes two EditText elements (username and password), two Buttons and two TextViews to show the server response.

Creating Classes

To send data to the server, Retrofit uses a Communicator and an Interface class. The Communicator methods create RestAdapters that use the Interfaces to perform a server request.

To create the Interface Class, right-click on the main package and select New -> Java Class. Call this class Interface and select Kind -> Interface.

This class contains the methods which will communicate with the API. First adding the methods for POST requests.

public interface Interface {

    //This method is used for "POST"
    @FormUrlEncoded
    @POST("/api.php")
    Call<ServerResponse> post(
            @Field("method") String method,
            @Field("username") String username,
            @Field("password") String password
    );

  //This method is used for "GET"
    @GET("/api.php")
    Call<ServerResponse> get(
            @Query("method") String method,
            @Query("username") String username,
            @Query("password") String password
    );

}

Three variables are sent to the API encoded as form data. You will build the ServerResponse class later, so don’t worry about any errors you might see.

The Communicator class performs the call and contains the methods which create the Rest Adapter.

Create a new class called Communicator and add this code:

public class Communicator {
    private static  final String TAG = "Communicator";
    private static final String SERVER_URL = "http://127.0.0.1/retrofit";

     public void loginPost(String username, String password){

        //Here a logging interceptor is created
        HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
        logging.setLevel(HttpLoggingInterceptor.Level.BODY);

        //The logging interceptor will be added to the http client
        OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
        httpClient.addInterceptor(logging);

        //The Retrofit builder will have the client attached, in order to get connection logs
        Retrofit retrofit = new Retrofit.Builder()
                .client(httpClient.build())
                .addConverterFactory(GsonConverterFactory.create())
                .baseUrl(SERVER_URL)
                .build(); Interface service = retrofit.create(Interface.class);

        Call<ServerResponse> call = service.post("login",username,password);

        call.enqueue(new Callback<ServerResponse>() {
            @Override
            public void onResponse(Call<ServerResponse> call, Response<ServerResponse> response) {
                BusProvider.getInstance().post(new ServerEvent(response.body()));
                Log.e(TAG,"Success");
            }

            @Override
            public void onFailure(Call<ServerResponse> call, Throwable t) {
                // handle execution failures like no internet connectivity
                BusProvider.getInstance().post(new ErrorEvent(-2,t.getMessage()));
            }
        });
    }

    public void loginGet(String username, String password){
        //Here a logging interceptor is created
        HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
        logging.setLevel(HttpLoggingInterceptor.Level.BODY);

        //The logging interceptor will be added to the http client
        OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
        httpClient.addInterceptor(logging);

        //The Retrofit builder will have the client attached, in order to get connection logs
        Retrofit retrofit = new Retrofit.Builder()
                .client(httpClient.build())
                .addConverterFactory(GsonConverterFactory.create())
                .baseUrl(SERVER_URL)
                .build();

        Interface service = retrofit.create(Interface.class);

        Call<ServerResponse> call = service.get("login",username,password);

        call.enqueue(new Callback<ServerResponse>() {
            @Override
            public void onResponse(Call<ServerResponse> call, Response<ServerResponse> response) {
                BusProvider.getInstance().post(new ServerEvent(response.body()));
                Log.e(TAG,"Success");
            }

            @Override
            public void onFailure(Call<ServerResponse> call, Throwable t) {
                // handle execution failures like no internet connectivity
                BusProvider.getInstance().post(new ErrorEvent(-2,t.getMessage()));
            }
        });
    }

Change SERVER_URL to the URL of your PHP server, I won’t cover setting up PHP in this tutorial, but you can find comprehensive instructions here.

Create a new class called BusProvider and add the following code:

public class BusProvider {

    private static final Bus BUS = new Bus();

    public static Bus getInstance(){
        return BUS;
    }

    public BusProvider(){}
}

In the MainActivity class, get the values from the EditText elements and use them as parameters to the server call. Change the MainActivity class to:

public class MainActivity extends AppCompatActivity {

    private Communicator communicator;
    private String username, password;
    private EditText usernameET, passwordET;
    private Button loginButtonPost, loginButtonGet;
    private TextView information, extraInformation;
    private final static String TAG = "MainActivity";
    public static Bus bus;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        communicator = new Communicator();

        usernameET = (EditText)findViewById(R.id.usernameInput);
        passwordET = (EditText)findViewById(R.id.passwordInput);
        //This is used to hide the password's EditText characters. So we can avoid the different hint font.
      passwordET.setTransformationMethod(new PasswordTransformationMethod());

        loginButtonPost = (Button)findViewById(R.id.loginButtonPost);
        loginButtonPost.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                username = usernameET.getText().toString();
                password = passwordET.getText().toString();
                usePost(username, password);
            }
        });

        loginButtonGet = (Button)findViewById(R.id.loginButtonGet);
        loginButtonGet.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                username = usernameET.getText().toString();
                password = passwordET.getText().toString();
                useGet(username, password);
            }
        });

        information = (TextView)findViewById(R.id.information);
        extraInformation = (TextView)findViewById(R.id.extraInformation);
    }

    private void usePost(String username, String password){
        communicator.loginPost(username, password);
        }

  private void useGet(String username, String password){
        communicator.loginGet(username, password);
      }
      }

Recapping what you have done so far. The values of the EditText elements are passed to the usePost() method. Then the communicator method is called and uses these values to create its Rest Adapter and perform the call.

Now that the posting method is almost complete, the application should handle responses from the server. For this, Retrofit uses classes called Model. For this application, the Model’s name is ServerResponse and is called inside the Interface’s method as a Callback, and when the RestAdapter is created. In both cases, it specifies the type of response expected from the server.

Create a new class called ServerResponse and add the code below:

public class ServerResponse {
    public class ServerResponse implements Serializable {
        @SerializedName("returned_username")
        private String username;
        @SerializedName("returned_password")
        private String password;
        @SerializedName("message")
        private String message;
        @SerializedName("response_code")
        private int responseCode;

        public ServerResponse(String username, String password, String message, int responseCode){
            this.username = username;
            this.password = password;
            this.message = message;
            this.responseCode = responseCode;
        }

        public String getUsername() {
            return username;
        }

        public void setUsername(String username) {
            this.username = username;
        }

        public String getPassword() {
            return password;
        }

        public void setPassword(String password) {
            this.password = password;
        }

        public String getMessage() {
            return message;
        }

        public void setMessage(String message) {
            this.message = message;
        }

        public int getResponseCode() {
            return responseCode;
        }

        public void setResponseCode(int responseCode) {
            this.responseCode = responseCode;
        }
    }
}

Retrofit’s models usually implement Serializable because they need to parse data from objects, in this case, from a JSONObject. This class declares variables, coupled with the name of the JSON key, whose data they handle.

To show the server responses, Retrofit uses ‘Events’. To get the server response, you need to create event classes. Create a new class called ServerEvent.

public class ServerEvent {
  private ServerResponse serverResponse;

  public ServerEvent(ServerResponse serverResponse) {
      this.serverResponse = serverResponse;
  }

  public ServerResponse getServerResponse() {
      return serverResponse;
  }

  public void setServerResponse(ServerResponse serverResponse) {
      this.serverResponse = serverResponse;
  }
}

When called, this class constructs a ServerResponse.

Create another class called ErrorEvent and add the following code:

public class ErrorEvent {
  private int errorCode;
  private String errorMsg;

  public ErrorEvent(int errorCode, String errorMsg) {
      this.errorCode = errorCode;
      this.errorMsg = errorMsg;
  }

  public int getErrorCode() {
      return errorCode;
  }

  public void setErrorCode(int errorCode) {
      this.errorCode = errorCode;
  }

  public String getErrorMsg() {
      return errorMsg;
  }

  public void setErrorMsg(String errorMsg) {
      this.errorMsg = errorMsg;
  }
}

These methods get more information about the server response or any errors. For this sample app, the Event classes are created but not used. Add these methods to the Communicator class, before the closing bracket:

...
@Produce
  public ServerEvent produceServerEvent(ServerResponse serverResponse) {
      return new ServerEvent(serverResponse);
  }

  @Produce
  public ErrorEvent produceErrorEvent(int errorCode, String errorMsg) {
      return new ErrorEvent(errorCode, errorMsg);
  }
  ...

The RestAdapter is now complete, the most important part of the code is:

...
Call<ServerResponse> call = service.post("login",username,password);

        call.enqueue(new Callback<ServerResponse>() {
            @Override
            public void onResponse(Call<ServerResponse> call, Response<ServerResponse> response) {
                // response.isSuccessful() is true if the response code is 2xx
                BusProvider.getInstance().post(new ServerEvent(response.body()));
                Log.e(TAG,"Success");
            }

            @Override
            public void onFailure(Call<ServerResponse> call, Throwable t) {
                // handle execution failures like no internet connectivity
                BusProvider.getInstance().post(new ErrorEvent(-2,t.getMessage()));
            }
        });
...

This specifies that the callback will have a ServerResponse structure and implements two overridden methods, success and failure. The first method is called when the client gets a response from the server and the second when the server is not found or when a connection error occurs.

Retrofit uses BusProvider to get data from the server whenever the success or failure method is called. A BusProvider is like a channel where every response is passed. Depending on the case, BusProvider posts a ServerEvent, from where you can get the desired data or an ErrorEvent which contains error information.

At one end of the channel is the Retrofit Builder which pushes the response data, and at the other, any activity which waits for data.

To make the MainActivity class wait for an event, first, you need to implement two methods. Add the following to the class:

@Override
  public void onResume(){
      super.onResume();
      BusProvider.getInstance().register(this);
  }

  @Override
  public void onPause(){
      super.onPause();
      BusProvider.getInstance().unregister(this);
  }

This causes the activity to wait for an event by registering it to the Event Bus, now the event needs to be caught. Since you built two types of event, ServerEvent and ErrorEvent, they both need to be caught by implementing two Subscribed methods. Add the following methods to MainActivity:

@Subscribe
  public void onServerEvent(ServerEvent serverEvent){
      Toast.makeText(this, ""+serverEvent.getServerResponse().getMessage(), Toast.LENGTH_SHORT).show();
      if(serverEvent.getServerResponse().getUsername() != null){
          information.setText("Username: "+serverEvent.getServerResponse().getUsername() + " || Password: "+serverEvent.getServerResponse().getPassword());
      }
      extraInformation.setText("" + serverEvent.getServerResponse().getMessage());
  }

  @Subscribe
  public void onErrorEvent(ErrorEvent errorEvent){
      Toast.makeText(this,""+errorEvent.getErrorMsg(),Toast.LENGTH_SHORT).show();
  }

Now the class is subscribed to the events provided by the Bus and depending on the type of Event, the user receives information.

Server side

PHP Script

For this sample app, the server consists of a simple PHP script. For simplicity, this script is not connected to a database. Create api.php and add the following:

<?php

    //Post Method here
    if(isset($_POST['method']) == 'login'){
        $username = $_POST['username'];
        $password = $_POST['password'];

        if($username == "admin" && $password == "admin"){
            $response = array('returned_username' => "-admin-",
                              'returned_password' => "-admin-",
                              'message' => "Your credentials are so weak [USING_POST]!",
                              'response_code' => "1");
               echo json_encode($response);

        }else{
            $response = array('response_code' => "-1",
                              'message' => "invalid username or password");                    
               echo json_encode($response);
        }
    }

The first part gets parameters from the POST method and if they are as expected, the statement is true and a JSON response sent back to the client. If the statement is not true, a JSON error message response is sent back to the client.

The second part is almost the same as the first, but the parameters are sent using the GET method. The response options are the same. If none of the methods are used, a general response with an error code is generated as a JSON Object.

Add the following to your PHP file:

//Get Method here
else if(isset($_GET['method']) == 'login'){
    $username = $_GET['username'];
    $password = $_GET['password'];

    if($username == "admin" && $password == "admin"){
        $response = array('returned_username' => "=admin=",
                          'returned_password' => "=admin=",
                          'message' => "Your credentials are so weak [USING_GET]!",
                          'response_code' => "1");
             echo json_encode($response);
    }else{
        $response = array('response_code' => "-1",
                          'message' => "invalid username or password");                    
           echo json_encode($response);
    }
}

//If no method
else{
        $response = array('response_code' => "-2",
        'message' => "invalid method");                    
             echo json_encode($response);
}
?>

Start your PHP server and the Android app. You should get the following results depending on what you type into the text fields and which button you tap.

Conclusion

After some initial setup, the retrofit library lets you add network calls to an Android application and I have used in most of the applications I develop. I hope you enjoy using the library as much as I do and I look forward to your comments and questions.

Frequently Asked Questions (FAQs) about Retrofit HTTP Client for Android and Java

What is the main difference between Retrofit and other HTTP clients?

Retrofit stands out from other HTTP clients due to its simplicity and ease of use. It is a type-safe HTTP client for Android and Java, which means it ensures type safety at compile time. This reduces the chances of runtime errors. Retrofit uses annotations to describe HTTP requests, which makes it easier to manipulate APIs. It also integrates seamlessly with converters for JSON, making it easier to decode data into usable objects.

How can I add headers to a Retrofit request?

Adding headers to a Retrofit request is straightforward. You can add them directly to the interface method using the @Headers annotation. For example:
@Headers("Cache-Control: max-age=640000")
@GET("widget/list")
Call<List<Widget>> widgetList();
In this example, the Cache-Control header is added to the HTTP request. You can also add multiple headers by separating them with a comma.

Can Retrofit handle synchronous and asynchronous requests?

Yes, Retrofit can handle both synchronous and asynchronous requests. Synchronous requests run on the same thread that they were started on, while asynchronous requests run on a background thread and deliver results on the main thread. You can choose the type of request based on your application’s needs.

How can I handle errors with Retrofit?

Retrofit provides several ways to handle errors. The most common way is to use the onFailure method of the Call interface. This method is called when a network exception occurred communicating to the server or when an unexpected exception occurred creating the request or processing the response.

How can I cancel a request with Retrofit?

You can cancel a request with Retrofit by calling the cancel() method on the Call object. This is useful when you want to cancel a request due to user action or to prevent unnecessary network usage.

How can I use Retrofit with RxJava?

Retrofit can be easily integrated with RxJava. You just need to add the RxJava adapter to Retrofit when building the Retrofit instance. Then, you can return an Observable, Flowable, Single, Completable or Maybe from your API interface methods.

Can I use Retrofit with a custom OkHttp client?

Yes, you can use Retrofit with a custom OkHttp client. You just need to pass the OkHttp client instance when building the Retrofit instance. This allows you to customize the HTTP client to suit your needs.

How can I log requests and responses with Retrofit?

You can log requests and responses with Retrofit using an OkHttp interceptor. The logging interceptor logs the request method, URL, headers, and body (if present) before the request is sent, and it logs the response code, headers, and body (if present) after the response is received.

How can I upload files with Retrofit?

You can upload files with Retrofit using the @Multipart annotation and the RequestBody object. You can create a RequestBody instance from a file and then pass it to the @Part annotation in your API interface method.

How can I download files with Retrofit?

You can download files with Retrofit using the @Streaming annotation. This annotation tells Retrofit to stream the response body. This is useful when downloading large files, as it prevents Retrofit from loading the entire file into memory.

Theodhor PandeliTheodhor Pandeli
View Author

Theodhor has just finished his Computer Engineering bachelor. He loves Android development and that is the field he is the most focused. He also has good knowledge in web development and Unity 3D.

chriswhttpjavalibraryretrofit
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week