Retrofit 2 — Constant, Default and Logic Values for POST and PUT Requests

This is another post in our content flash series celebrating the release of Retrofit 2.0. It explains how to add constant, default and logic (which are computed for some input) values to the HTTP request body for POST and PUT requests. Sounds complicated? We promise, it won't be!

Before we jump in, take a quick look at our list of other Retrofit posts:

Retrofit Series Overview

  1. Introduction to Call Adapters (Coming soon)
  2. Custom Call Adapter to Separate OnResponse Callback (Coming soon)
  3. How to Integrate RxJava 1.x Call Adapter (Coming soon)
  4. How to Integrate RxJava 2.x Call Adapter (Coming soon)
  5. How to Integrate Guava Call Adapter (Coming soon)
  6. Custom Call Adapter to Separate Network and Gson Errors (Coming soon)
  1. Callbacks (Coming soon)
  2. Annotations (Coming soon)
  3. Fluent Interface with Builders (Coming soon)

If you haven't worked with Retrofit before, please go back to the beginning and understand how we create services and execute requests. If you already got experience, keep reading!

Adding a Request for a Feedback Function

Let's assume your boss gave you the task to implement a feedback function to your Android app. The feedback function should allow the user to give some text input along with some general device data, which is collected automatically. Your backend developer has already done the work and described the endpoint to you. The endpoint expects the following:

URL: /feedback  
Method: POST  
Request Body Params (Required):

- osName=[String]
- osVersion=[Integer]
- device=[String]
- message=[String]
- userIsATalker=[boolean]

Response: 204 (Empty Response Body)  

The first three values are data about the device the user is sending his feedback from. The fourth one is his actual message. The last one is a flag, which signals if the message is especially long.

Simple Approach

The first attempt might be to transfer the REST API documentation over to our Retrofit service declaration:

@FormUrlEncoded
@POST("/feedback")
Call<ResponseBody> sendFeedbackSimple(  
    @Field("osName") String osName,
    @Field("osVersion") int osVersion,
    @Field("device") String device,
    @Field("message") String message,
    @Field("userIsATalker") Boolean userIsATalker);

Next, you'd hook it up to the onclick method of the submit button of the feedback form. There you'd gather the data, compute the logic value and call the service method with all of the data's values:

private void sendFeedbackFormSimple(@NonNull String message) {  
    // create the service to make the call, see first Retrofit blog post
    FeedbackService taskService = ServiceGenerator.create(FeedbackService.class);

    // create flag if message is especially long
    boolean userIsATalker = (message.length() > 200);

    Call<ResponseBody> call = taskService.sendFeedbackSimple(
            "Android",
            android.os.Build.VERSION.SDK_INT,
            Build.MODEL,
            message,
            userIsATalker
    );

    call.enqueue(new Callback<ResponseBody>() {
        ...
    });
}

That's it! The programmer in you is satisfied with the result, but you can hear your software engineering professor scream in agony. As you can see above in the method declaration of sendFeedbackFormSimple(), the only true variable is the message of the user. The osName variable will not change ever and is actually a constant (the Android app will always have Android as operating system). Next, the osVersion and device are default strings depending on the device model. The userIsATalker is a logic value, which is calculated the same way every time.

In this case, it's not a big issue, since you only have one feedback form in your app. But what if this would be an endpoint you call from multiple places in your app (for example updating the user profile)? Then you'd have a bunch of duplicated code. Of course, you could introduce some constants and a helper method, which keeps the logic in one place, but there is a cleaner way. In the next section we'll show you how you can create a POST/PUT request with just the one true parameter, while the rest gets set automatically.

Advanced Approach With Passing the Only True Variable

First, we've to change the service declaration. The request now is made with a Java object (Retrofit will automatically serialize it):

@POST("/feedback")
Call<ResponseBody> sendFeedbackConstant(@Body UserFeedback feedbackObject);  

The parameter is an instance of the UserFeedback class, which we haven't shown you yet. It's where the magic happens:

public class UserFeedback {

    private String osName = "Android";
    private int osVersion = android.os.Build.VERSION.SDK_INT;
    private String device = Build.MODEL;
    private String message;
    private boolean userIsATalker;

    public UserFeedback(String message) {
        this.message = message;
        this.userIsATalker = (message.length() > 200);
    }

    // getters & setters
    // ...
}

The constructor for this class only contains the true variable message, everything else gets set or computed automatically! Since we're passing the entire object, all the values will be sent.

This makes the request call back to our UI code much leaner as well:

private void sendFeedbackFormAdvanced(@NonNull String message) {  
    FeedbackService taskService = ServiceGenerator.create(FeedbackService.class);

    Call<ResponseBody> call = taskService.sendFeedbackConstant(new UserFeedback(message));

    call.enqueue(new Callback<ResponseBody>() {
        ...
    });
}

Even if the app has multiple locations where this request is made, all the constant, default and logic values are computed in one single place. The request calls only pass the single variable. This makes your code much more robust against future changes (and easier to read)!

Feel free to send us your questions via Twitter: @futurestud_io or in the comment section below.


Explore the Library

Find interesting tutorials and solutions for your problems.

Miscellaneous