Java Basics for Retrofit — Fluent Interface with Builders

Every good software product follows some architecture and implementation patterns. In the past few decades, a lot of different ideas emerged. New and even experienced developers can still be overwhelmed by the amount of different patterns and when they should be applied.

In this tutorial we’ll introduce you to two patterns utilized by Retrofit: the builder pattern and the fluent interface. They’re often used together and in this tutorial you’ll learn why that’s not a surprise.

Retrofit Series Overview

Builder Pattern

Before we jump into Retrofit’s usage of the combination of both patterns (fluent interface & builder), let’s look at them separately in a simplified scenario. For the purpose of this tutorial we’re developing a Future Studio University Android app. When a new student enrolls for a semester, we need to check his data and payment input and, if successful, create a new account for him. Traditionally, this could look like this:

Student student = new Student();  
student.setName("Norman");  
student.setEmail("norman@futurestud.io");  
student.setIpAddress("127.0.0.1");  
student.setCreditCardNo("4111 1111 1111 1111");

// check input
if (Validator.isValidStudent(student)) {  
    // todo create account
}
else {  
    // todo show error message
}

You might be wondering why we need the IP address of the user. Since Future Studio is based in the EU, we need to apply different tax rates depending on the user’s location. To make it simple for the user, we pre-select the country selection with the one he or she is currently located in.

The code snippet above has a few weaknesses. It’s not obvious which values are required and which are optional input values. We could change the constructor to take the required parameter while setting optional ones with setter methods. But that would blow up the constructor and make it less readable. Additionally, it would still not guarantee us that the isValidStudent() validation is called, which checks the IP address and payment information.

The builder pattern, which is designed to simplify object creation, helps with both of these issues. Instead of creating a Student object directly, let’s use a builder StudentBuilder:

public class StudentBuilder {  
    private Student student;

    private StudentBuilder() {
        student = new Student;
    }

    public static StudentBuilder studentBuilder() {
        return new StudentBuilder();
    }

    public StudentBuilder setEmail(String email) {
        student.setEmail(email);
        return this;
    }

    public StudentBuilder setName(String name) {
        student.setName(name);
        return this;
    }

    public StudentBuilder setIpAddress(String ipAddress) {
        student.setIpAddress(ipAddress);
        return this;
    }

    public StudentBuilder setCreditCardNo(String creditCardNo) {
        student.setCreditCardNo(creditCardNo);
        return this;
    }

    public Student build() throws Exception {
        // set some clever default values
        student.setVatRate(10);

        // check input
        if (Validator.isValidStudent(student)) {
            return student;
        }
        else {
            throw new Exception("detailed error message would be here");
        }
    }
}

This looks like quite a bit of code on the first look. Nevertheless, it offers us a few advantages. First, we made sure the developer validates the student before creating an account with the student object. Second, we can add some clever default values and logic, which can be quite helpful in more complex scenarios (like Retrofit).

Once we’ve set up the builder, the code to create a new student looks almost the same:

StudentBuilder builder = new StudentBuilder();  
builder = builder.setName("Norman");  
builder = builder.setEmail("norman@futurestud.io");  
builder = builder.setIpAddress("127.0.0.1");  
builder = builder.setCreditCardNo("4111 1111 1111 1111");

Student student;  
try {  
    student = builder.build();
    // todo create account
}
catch (Exception e) {  
    // todo show error message
}

However, it’s not that choice of the developer anymore to check the user’s input. Excellent!

Unfortunately, we introduced a few lines of ugly builder = builder.set() code. It’s time for the fluent interface to step in and save the day by making it look nicer.

Fluent Interface

Continuing on the example of the previous section, we’re trying to make our builder code better readable by applying a fluent interface to it. It’s important to understand that we’re not really changing any logic or behavior. Fluent interface is a pattern, which usually relies on method chaining. Each method call returns the own object, or the next logic object in our creation chain.

If we apply this concept to our StudentBuilder our code would change to:

StudentBuilder builder =  
    new StudentBuilder()
        .setName("Norman");
        .setEmail("norman@futurestud.io");
        .setIpAddress("127.0.0.1");
        .setCreditCardNo("4111 1111 1111 1111");

Student student;  
try {  
    student = builder.build();
    // todo create account
}
catch (Exception e) {  
    // todo show error message
}

That looks a lot nicer and still contains the builder pattern goodies from our previous section. You can see quite nicely why the two patterns are often used together.

In this case, the fluent interface just connects function calls between the StudentBuilder object. However, it’s even more powerful when it cascades through multiple objects. For example, we could change our code to:

try {  
    new StudentBuilder()
            .setName("Norman");
            .setEmail("norman@futurestud.io");
            .setIpAddress("127.0.0.1");
            .setCreditCardNo("4111 1111 1111 1111");
            .build()
            .saveStudent();
}
catch (Exception e) {  
    // todo show error message
}

Here, we don’t create a specific Student object. We just call build() on our StudentBuilder. Thanks to the fluent interface and the returned Student object we can directly continue with our calls and create the student account.

In more complex implementations, the fluent interface cascades through multiple steps. You can see an excellent example on Android with the image loading library Glide.

Retrofit’s Builder with Fluent Interface

In the previous two sections you’ve learned what the fluent interface and the builder pattern are used for and why they’re often appearing together. Retrofit’s object creation for an instance of the Retrofit class deals with the same issues as we’ve described above. It also sets some default values in the builder (like the OkHttp network layer) while exposing additional configuration to us in a clean way.

Let’s look at a specific example:

HttpLoggingInterceptor logging =  
        new HttpLoggingInterceptor()
                .setLevel(HttpLoggingInterceptor.Level.BODY);

Retrofit retrofit =  
        new Retrofit.Builder()
                .addConverterFactory(new PolymorphicCustomConverter())
                .addConverterFactory(GsonConverterFactory.create())
                .client(
                        new OkHttpClient.Builder().addInterceptor(logging).build()
                )
                .baseUrl("http://api.github.com/)
                .build();

We’re utilizing the Retrofit.Builder class to set some configurations, like the base URL and the converters, and finally get the Retrofit instance by calling build(). Theoretically, we could directly continue on the Retrofit object for further actions, like creating an API client.

Hopefully, you noticed our little easter egg as well. We snuck another builder into the code snippet: OkHttpClient.Builder(), which creates the OkHttp network client with integrated logging of the HTTP body.

Outlook

In this tutorial you’ve learned about the builder and fluent interface patterns and how they’re used in Retrofit.

Obviously, this was only a quick introduction to these two patterns. There are more details to explore, if you want to get deeper into them. We can highly recommend the Head First Design Patterns book for a detailed overview over software design patterns.

If you’ve feedback or a question, let us know in the comments or on twitter @futurestud_io.

Make it rock & enjoy coding!


Further Readings

Explore the Library

Find interesting tutorials and solutions for your problems.