Builder pattern

In the previous example, we used the setFillColor() method to set the fill color of the custom view, but suppose we will have many other parameters, the code might get a bit messy with all the setters.

Let's create a simple example: instead of having one single background color, we'll have four different colors and we'll draw a gradient on our view:

Let's start by defining the four different colors and their setters as follows:

private int topLeftColor = DEFAULT_FILL_COLOR; 
private int bottomLeftColor = DEFAULT_FILL_COLOR; 
private int topRightColor = DEFAULT_FILL_COLOR; 
private int bottomRightColor = DEFAULT_FILL_COLOR; 
private boolean needsUpdate = false;

public void setTopLeftColor(int topLeftColor) { this.topLeftColor = topLeftColor; needsUpdate = true; } public void setBottomLeftColor(int bottomLeftColor) { this.bottomLeftColor = bottomLeftColor; needsUpdate = true; } public void setTopRightColor(int topRightColor) { this.topRightColor = topRightColor; needsUpdate = true; } public void setBottomRightColor(int bottomRightColor) { this.bottomRightColor = bottomRightColor; needsUpdate = true; }

We also added a boolean to check if we have to update the gradient. Let's ignore thread synchronization here as it's not the main purpose of this example.

Then, we've added a check for this boolean on the onDraw() method and, in the case it's needed, it'll regenerate the gradient:

@Override
protected void onDraw(Canvas canvas) {
if (needsUpdate) {
int[] colors = new int[] {topLeftColor, topRightColor,
bottomRightColor, bottomLeftColor};

LinearGradient lg = new LinearGradient(0, 0, getWidth(),
getHeight(), colors, null, Shader.TileMode.CLAMP);

backgroundPaint.setShader(lg);
needsUpdate = false;
}

canvas.drawRect(0, 0, getWidth(), getHeight(), backgroundPaint);
super.onDraw(canvas);
}

It's a bad practice to create new object instances on the onDraw() method. Here it is only done once, or every time we're changing the colors. If we were changing the color constantly, this would be a bad example as it'll be constantly creating new objects, polluting the memory, and triggering the Garbage Collector (GC). There will be more on performance and memory in Chapter 7, Performance Considerations.

We have to update the code of our Activity to set these new colors:

public class MainActivity extends AppCompatActivity { 
    private static final int BRIGHT_GREEN = 0xff00ff00; 
    private static final int BRIGHT_RED = 0xffff0000; 
    private static final int BRIGHT_YELLOW = 0xffffff00; 
    private static final int BRIGHT_BLUE = 0xff0000ff; 
 
    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
 
        LinearLayout linearLayout = new LinearLayout(this); 
        linearLayout.setLayoutParams( 
                new LinearLayout.LayoutParams(ViewGroup.
LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT)); OwnCustomView customView = new OwnCustomView(this); customView.setTopLeftColor(BRIGHT_RED); customView.setTopRightColor(BRIGHT_GREEN); customView.setBottomLeftColor(BRIGHT_YELLOW); customView.setBottomRightColor(BRIGHT_BLUE); linearLayout.addView(customView); setContentView(linearLayout); } }

As we can see, we've used four setters to set the colors. If we've got more parameters, we could use more setters, but one of the issues of this approach is that we have to take care of thread synchronization and the object might be in an unstable state until all calls are done.

Another option is to add all the parameters to the constructor, but that is not a good solution either. It'd make our job more complex, as it'll be hard to remember the order of the parameters or, in the case where we had optional, to create many different constructors or passing null references that make our code harder to read and maintain.

Check the full source code of this example in the Example06-BuilderPattern-NoBuilder folder of the GitHub repository.

Now that we've introduced the issue, let's solve it by implementing the Builder pattern on our custom view. We start by creating a public static class inside our custom view that will build it as follows:

public static class Builder { 
    private Context context; 
    private int topLeftColor = DEFAULT_FILL_COLOR; 
    private int topRightColor = DEFAULT_FILL_COLOR; 
    private int bottomLeftColor = DEFAULT_FILL_COLOR; 
    private int bottomRightColor = DEFAULT_FILL_COLOR; 
 
    public Builder(Context context) { 
        this.context = context; 
    } 
 
    public Builder topLeftColor(int topLeftColor) { 
        this.topLeftColor = topLeftColor; 
        return this; 
    } 
 
    public Builder topRightColor(int topRightColor) { 
        this.topRightColor = topRightColor; 
        return this; 
    } 
 
    public Builder bottomLeftColor(int bottomLeftColor) { 
        this.bottomLeftColor = bottomLeftColor; 
        return this; 
    } 
 
    public Builder bottomRightColor(int bottomRightColor) { 
        this.bottomRightColor = bottomRightColor; 
        return this; 
    } 
 
    public OwnCustomView build() { 
        return new OwnCustomView(this); 
    } 
} 

We also create a new private constructor that only accepts an OwnCustomView.Builder object:

private OwnCustomView(Builder builder) { 
    super(builder.context); 
 
    backgroundPaint = new Paint(); 
    backgroundPaint.setStyle(Paint.Style.FILL); 
 
    colorArray = new int[] { 
            builder.topLeftColor, 
            builder.topRightColor, 
            builder.bottomRightColor, 
            builder.bottomLeftColor 
    }; 
 
    firstDraw = true; 
 } 

We've removed other constructors for clarity. Also at this point, we create the array of colors based on the colors that the builder object has and a boolean to know if it's the first time it'll be drawn or not.

This will be useful to instantiate the LinearGradient object only once and avoid creating many instances:

@Override 
    protected void onDraw(Canvas canvas) { 
        if (firstDraw) { 
            LinearGradient lg = new LinearGradient(0, 0, getWidth(),
getHeight(), colorArray, null, Shader.TileMode.CLAMP); backgroundPaint.setShader(lg); firstDraw = false; } canvas.drawRect(0, 0, getWidth(), getHeight(),
backgroundPaint); super.onDraw(canvas); }

Now, once the object is created we can't change its colors, but we don't have to worry about thread synchronization and the object's state.

To make it work, let's update the code on our Activity as well:

public class MainActivity extends AppCompatActivity { 
    private static final int BRIGHT_GREEN = 0xff00ff00; 
    private static final int BRIGHT_RED = 0xffff0000; 
    private static final int BRIGHT_YELLOW = 0xffffff00; 
    private static final int BRIGHT_BLUE = 0xff0000ff; 
     
    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
 
        LinearLayout linearLayout = new LinearLayout(this); 
        linearLayout.setLayoutParams( 
                new LinearLayout.LayoutParams(ViewGroup.
LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT)); OwnCustomView customView = new OwnCustomView.Builder(this) .topLeftColor(BRIGHT_RED) .topRightColor(BRIGHT_GREEN) .bottomLeftColor(BRIGHT_YELLOW) .bottomRightColor(BRIGHT_BLUE) .build(); linearLayout.addView(customView); setContentView(linearLayout); } }

Using the Builder pattern, our code is cleaner and the object is constructed or built when we've set all the properties and this will become even handier if the custom view has more parameters.

The full example source code can be found in the Example07-BuilderPattern folder in the GitHub repository.