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
- Getting Started and Creating an Android Client
- Basics of API Description
- Creating a Sustainable Android Client
- URL Handling, Resolution and Parsing
- How to Change API Base Url at Runtime
- Multiple Server Environments (Develop, Staging, Production)
- Share OkHttp Client and Converters between Retrofit Instances
- Upgrade Guide from 1.9
- Beyond Android: Retrofit for Java Projects
- How to use OkHttp 3 with Retrofit 1
- Synchronous and Asynchronous Requests
- Send Objects in Request Body
- Add Custom Request Header
- Manage Request Headers in OkHttp Interceptor
- Dynamic Request Headers with @HeaderMap
- Multiple Query Parameters of Same Name
- Optional Query Parameters
- Send Data Form-Urlencoded
- Send Data Form-Urlencoded Using FieldMap
- How to Add Query Parameters to Every Request
- Add Multiple Query Parameter With QueryMap
- How to Use Dynamic Urls for Requests
- Constant, Default and Logic Values for POST and PUT Requests
- Cancel Requests
- Reuse and Analyze Requests
- Optional Path Parameters
- How to Send Plain Text Request Body
- Customize Network Timeouts
- How to Trust Unsafe SSL certificates (Self-signed, Expired)
- Dynamic Endpoint-Dependent Interceptor Actions
- How to Update Objects on the Server (PUT vs. PATCH)
- How to Delete Objects on the Server
- Introduction to (Multiple) Converters
- Adding & Customizing the Gson Converter
- Implementing Custom Converters
- How to Integrate XML Converter
- Access Mapped Objects and Raw Response Payload
- Supporting JSON and XML Responses Concurrently
- Handling of Empty Server Responses with Custom Converter
- Send JSON Requests and Receive XML Responses (or vice versa)
- Unwrapping Envelope Responses with Custom Converter
- Wrapping Requests in Envelope with Custom Converter
- Define a Custom Response Converter
- Callbacks
- Annotations
- Fluent Interface with Builders
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!