Tuesday, April 8, 2014

Applying decorator pattern to decorate views on Android

Hi there!

Today i'm gonna show how to apply the design pattern decorator to android's views making use of its dependency injection's concept. I thought it was a nice, clear and simple way to decorate views and that's my motivator to share it with you. I'm assuming you know the decorator pattern already and are looking for real examples involving android. If you are trying to understand the concept behind it, this is also a good article but you may need to learn the fundamentals first. There are a bunch of good books out there. I personally recommend head first.

In this example i'll try to show how to decorate a view with icons dynamically. Something similar to eclipse's tree decoration while some compile error or warning appears on the package structure. We will end up with something like that:

Error/warning tree 
decoration in eclipse


Our view decoration 
examplein Android

Let's visualize the structure by defining the UML-diagram

 

First create a blank android's project

Put some icons of your choice in the folder drawable-yourChoice. In my example i took the drawable-xhdpi. I took the icons directly from my eclipse package. You may use your own icons.

Layouting the view

Ok, now copy this code into your activity_main.xml layout:
< 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=".MainActivity" >

    < ImageView
        android:id="@+id/middle"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:src="@drawable/ic_launcher" / >

    < ImageView
        android:id="@+id/leftTop"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_alignLeft="@+id/middle"
        android:layout_alignTop="@+id/middle"
         / >

    < ImageView
        android:id="@+id/rightTop"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_alignRight="@+id/middle"
        android:layout_alignTop="@+id/middle"
         / >

    < ImageView
        android:id="@+id/leftBottom"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_alignBottom="@+id/middle"
        android:layout_alignLeft="@+id/middle"
         / >

    < ImageView
        android:id="@+id/rightBottom"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_alignBottom="@+id/middle"
        android:layout_alignRight="@+id/middle"
         / >

    < ImageView
        android:id="@+id/middleTop"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_alignTop="@+id/middle"
        android:layout_centerHorizontal="true"
         / >

< / RelativeLayout >

The common decorator interface

Well ok, let's start by defining the common decorator interface:
/**
 * The common decorable interface to be used while implementing decorators.
 * @author Ricardo Ferreira
 * @version 1.0
 * @since 07/04/2014
 */
public interface Decorable {
    /** implements the concrete decorator's behavior */
    void decorate();
}

Defining the decorators contract

This class defines what kind of object we want to decorate. In our case an android's view which belongs to the Android's API itself:
import java.util.Arrays;
import android.content.Context;
import android.view.View;
/**
 * Used to define the decorator's contract. In this case to decorate a view with icons
 * @author Ricardo Ferreira
 * @version 1.0
 * @since 07/04/2014
 */
public abstract class AbstractIconDecorator implements Decorable{
    
    protected View view;
    protected View[] views;
    protected int drawableId;
    protected Context context;
    /**
     * Concrete classes must call this constructor to conform the decorator's contract.
     * @param context the app's context
     * @param view the view to be decorated
     * @param drawableId the drawable id dependency over R.drawable.yourId to be set 
     * as background to the view of this constructor.
     */
    public AbstractIconDecorator(Context context, View view, int drawableId){
        super();
        this.view = view;
        this.context = context;
        this.drawableId = drawableId;
        decorate();
    }
    /**
     * Concrete classes must call this constructor to conform the undo decorator's contract.
     * @param context context the app's context
     * @param views the views to be undone. 
     */
    public AbstractIconDecorator(Context context,View[] views){
        super();
        this.context = context;
        this.views = Arrays.copyOf(views, views.length);
        decorate();
    }
}

Implementing a master icon decorator

This is a very nice implementation that takes advantage of android's concept of dependency injection over the resource class R.drawable.myId. Using it, we don't have to implement a lot of decorators. Instead of it, we implement just one and inject our dependencies according to our needs as you will see.
import android.content.Context;
import android.view.View;
/**
 * Use this class to decorate views with icons thru dependency injection passing R.drawable.yourId
 * @author Ricardo Ferreira
 * @version 1.0
 * @since 07/04/2014
 */
public class IconDecorator extends AbstractIconDecorator{

    /**
     * Creates an universal icon decorator to be used while dealing with view's decoration.
     * @param context the app's context
     * @param view the view to be decorated
     * @param drawableId the drawable id dependency over R.drawable.yourId to be set 
     * as background to the view of this constructor.
     */
    public IconDecorator(Context context,View view, int drawableId){
        super(context, view, drawableId);
    }
    @Override
    public void decorate() {
        view.setBackground(context.getResources().getDrawable(drawableId));
    } 
} 

Implementing an undo decorator to revert decorations

This is a special kind of decoration. I will decorate the main icon(the android's green robot icon) with "nothing" making all decorations disappear.
/**
 * Use this decorator to undo decoration by passing the view to be undone.
 * @author Ricardo Ferreira
 * @version 1.0
 * @since 07/04/2014
 */
public class ClearIconDecorator extends AbstractIconDecorator{

    /**
     * Creates an undo decorator.
     * @param context the app's context
     * @param views the views that has been decorated to be undone.
     */
    public ClearIconDecorator(Context context,View[] views){
        super(context,views);
    }
    @Override
    public void decorate() {
        for (View view : views) {
            view.setBackground(null);
        }
    } 
}

The main activity

Now the final step before running it. In this example i am simulating the decoration over clicks. But in your professional applications those decorations may occur while something go wrong or right or you may need to give some visual user feedbacks while doing something. Use your imagination ;)
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;

public class MainActivity extends Activity implements OnClickListener{

    private View middle;
    private View leftTop;
    private View leftBottom;
    private View rightBottom;
    private View rightTop;
    private View middleTop;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        middle = findViewById(R.id.middle);
        leftTop = findViewById(R.id.leftTop);
        leftBottom = findViewById(R.id.leftBottom);
        rightBottom = findViewById(R.id.rightBottom);
        rightTop = findViewById(R.id.rightTop);
        middleTop= findViewById(R.id.middleTop);
        
        leftTop.setOnClickListener(this);
        leftBottom.setOnClickListener(this);
        rightBottom.setOnClickListener(this);
        rightTop.setOnClickListener(this);
        middle.setOnClickListener(this);
        middleTop.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            
        case R.id.leftTop:
            new ErrorIconDecorator(view);
            break;
            
        case R.id.leftBottom:
            new WarningIconDecorator(view);
            break;
            
        case R.id.rightTop:
            new InfoIconDecorator(view);
            break;
            
        case R.id.rightBottom:
            new QuickFixIconDecorator(view);
            break;
            
        case R.id.middleTop:
            new IconDecorator(this,view, R.drawable.newpack_wiz);
            break;
            
        case R.id.middle:
            final View[] views = new View[]{leftTop,leftBottom,rightTop,rightBottom,middleTop};
            new UndoIconDecorator(views);
            break;
        default:
            break;
        }
    }
    
    // you may define specific decorators to your convenience... 
    // ... or you may use directly the universal decorator IconDecorator(...). 
    // It is up to your. Here i'm showing both examples
    
    public class WarningIconDecorator{
        public WarningIconDecorator(View view){
            new IconDecorator(MainActivity.this, view, R.drawable.warning_obj);
        }
    }
    
    public class ErrorIconDecorator {
        public ErrorIconDecorator(View view){
            new IconDecorator(MainActivity.this, view, R.drawable.error);
        }
    }
    
    public class InfoIconDecorator {
        public InfoIconDecorator(View view){
            new IconDecorator(MainActivity.this, view, R.drawable.information);
        }
    }
    
    public class PackageDecorator {
        public PackageDecorator(View view){
            new IconDecorator(MainActivity.this, view, R.drawable.package_obj);
        }
    }
    
    public class QuickFixIconDecorator {
        public QuickFixIconDecorator(View view){
            new IconDecorator(MainActivity.this, view, R.drawable.quickfix_error_obj);
        }
    }
    
    public class ErrorWarningDecorator {
        public ErrorWarningDecorator(View view){
            new IconDecorator(MainActivity.this, view, R.drawable.errorwarning_tab);
        }
    }
    
    public class UndoIconDecorator{
        public UndoIconDecorator(View[] views){
            new ClearIconDecorator(MainActivity.this, views);
        }
    }
}
So that's all. Hope you like it.
 
😱👇 PROMOTIONAL DISCOUNT: BOOKS AND IPODS PRO ðŸ˜±ðŸ‘‡

Be sure to read, it will change your life!
Show your work by Austin Kleonhttps://amzn.to/34NVmwx

This book is a must read - it will put you in another level! (Expert)
Agile Software Development, Principles, Patterns, and Practiceshttps://amzn.to/30WQSm2

Write cleaner code and stand out!
Clean Code - A Handbook of Agile Software Craftsmanship: https://amzn.to/33RvaSv

This book is very practical, straightforward and to the point! Worth every penny!
Kotlin for Android App Development (Developer's Library): https://amzn.to/33VZ6gp

Needless to say, these are top right?
Apple AirPods Pro: https://amzn.to/2GOICxy

😱👆 PROMOTIONAL DISCOUNT: BOOKS AND IPODS PRO ðŸ˜±ðŸ‘†