Follow treslines by email clicking Here!

Monday, February 24, 2014

MVC applied on Android - A practical approach

Hi there!

Today i'm gonna show, how i apply the MVC pattern while developing android applications. I found it so usefull and it separates the concerns in such a nice way, that i think it could be also helpful for others out there.

Some people don't know why we should do that or what is the advantage of it exactly. So before you ask me that ;) here is the explanation of Martin Follower, which a share:

Definition of MVC

"At the heart of MVC is what I call Separated Presentation. The idea behind Separated Presentation is to make a clear division between domain objects that model our perception of the real world, and presentation objects that are the GUI elements we see on the screen. Domain objects should be completely self contained and work without reference to the presentation, they should also be able to support multiple presentations, possibly simultaneously. The fully article can be read here."

And that is exactly what i'm trying to show bellow. A way to decouple View from Model. So in this approach bellow, my View is designed entirely without any logic and don't care about the Model. The model istself contains alls business logics but don't know the View. The Controller is the mediator and is the only one who knows both Model and View. But it does not implement UI or Business Logic. Instead of that, it delegates (mediates) to its delegatees.


Class diagram

To show how it could looks like, i've created a simple calculator project. Lets take a look at the UML and Sequence-diagram first:






Sequence diagram

Und here the sequence diagram:






In the sequence diagram above, the activity representes the HI (Human Interface - Hardware), the View represents the GUI (Grafical User Interface), the Model is responsible for all business logics, the Controller is the mediator and the Calculator, Reseter, DbUpdater and ViewUpdater are the Delegatees from the Controller. They may have access to Model, to View or to both of them.


Creating the project

Create a simple blank project and define the structure like this:

Here you can already see the first advantage of it. We have separeted packages and a clear structure of our architecture already. It makes a lot easier to explain things especially if you are working with distributed teams. For each activity you will have a group of 3 elementes. (MVC)


Creating the Model

The model contains just business logics. In our case a simple calculation. Which can be tested separeted thru junit tests for example. It is not allowed to have any reference to our View. Our example looks like this:


// MODEL'S RESPONSABILITY: 
//
// ACCESS DB USING DAO's AND COMPUTE BUSINESS LOGICS ONLY. IT COMMUNICATES ONLY
// WITH THE CONTROLLER.
//
// IMPORTANT: IT DOESN'T CARE ABOUT VIEW AND IT DOESN'T KNOW NOTHING ABOUT THE VIEW
public class CalculatorModel implements Serializable {

    private static final long serialVersionUID = 1L;
    
    // YOU MAY STORE THING IN THE DB AND QUERY FOR INFORMATION
    // IN THIS SIMPLE EXAMPLE WE JUST HAVE A SIMPLE CALCULATOR MODEL
    private int lastResult;
    private int actualResult;

    public void add2Numbers(int firstNumber, int secondNumber) {
        this.lastResult = firstNumber + secondNumber;
        this.actualResult = this.lastResult;
    }

    public int getActualResult() {
        return actualResult;
    }

    public int getLastResult() {
        return lastResult;
    }

    public void resetActualResult() {
        this.actualResult = 0;
    }

}


Creating the totally dummy View

The view is totally dumm and has no logic inside of it. It contains just the UI objects that will be shown to the user later. Something like this:


define a layout like this for it:
< relativelayout android:layout_height="match_parent" android:layout_width="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=".CalculatorActivity" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">

    < textview android:id="@+id/textView1" android:layout_height="wrap_content" android:layout_width="wrap_content" android:text="Calulator">

    < edittext android:ems="10" android:id="@+id/firstNumberField" android:layout_alignleft="@+id/textView1" android:layout_alignparentright="true" android:layout_below="@+id/textView1" android:layout_height="wrap_content" android:layout_margintop="16dp" android:layout_width="wrap_content"/ >

    < edittext android:ems="10" android:id="@+id/secondNumberField" android:layout_alignleft="@+id/firstNumberField" android:layout_alignright="@+id/firstNumberField" android:layout_below="@+id/firstNumberField" android:layout_height="wrap_content" android:layout_margintop="18dp" android:layout_width="wrap_content" / >

    < button android:id="@+id/calculateButton" android:layout_alignleft="@+id/secondNumberField" android:layout_below="@+id/secondNumberField" android:layout_height="wrap_content" android:layout_margintop="24dp" android:layout_width="wrap_content" android:text="calculate"/ >

    < textview android:id="@+id/calculationResult" android:layout_alignleft="@+id/calculateButton" android:layout_alignright="@+id/calculateButton" android:layout_below="@+id/calculateButton" android:layout_height="wrap_content" android:layout_margintop="28dp" android:layout_width="wrap_content" android:text="Result" android:textappearance="?android:attr/textAppearanceLarge"/ >

    < textview android:id="@+id/lastCalculationResult" android:layout_alignbottom="@+id/textView1" android:layout_alignright="@+id/firstNumberField" android:layout_height="wrap_content" android:layout_marginleft="37dp" android:layout_torightof="@+id/textView1" android:layout_width="wrap_content" android:text="Last Calculation Result"/ >

    < /textview>< /textview>< /button>< button android:id="@+id/resetButton" android:layout_alignbottom="@+id/calculateButton" android:layout_alignright="@+id/secondNumberField" android:layout_height="wrap_content" android:layout_width="wrap_content" android:text="Reset"/ >

< /relativelayout>

Then create a class in the view package like this:
// VIEW'S RESPONSABILITY: 
//
// INSTANTIATE ALL COMPONENTS AND BIND IT TO THE CONTROLLER. IT COMMUNICATES ONLY WITH
// THE CONTROLLER.
//
// IMPORTANT: IT DOESN'T CARE ABOUT MODEL AND IT DOESN'T KNOW NOTHING ABOUT THE MODEL
public class CalculatorView {
    
    private EditText firstNumberField;
    private EditText secondNumberField;
    private TextView lastCalculationResult;
    private TextView calculationResult;
    private Button calculateButton;
    private Button resetButton;
    protected Activity context;

    public CalculatorView(Activity context) {
        this.context = context;
        defineViewOrientation();
        firstNumberField = (EditText) context.findViewById(R.id.firstNumberField);
        secondNumberField = (EditText) context.findViewById(R.id.secondNumberField);
        calculationResult = (TextView) context.findViewById(R.id.calculationResult);
        lastCalculationResult = (TextView) context.findViewById(R.id.lastCalculationResult);
        calculateButton = (Button) context.findViewById(R.id.calculateButton);
        resetButton = (Button) context.findViewById(R.id.resetButton);
    }
    
    private void defineViewOrientation(){
        context.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
    }

    public void bind(CalculatorController listener) {
        calculateButton.setOnClickListener(listener);
        resetButton.setOnClickListener(listener);
    }
    
    public EditText getFirstNumberField() {
        return firstNumberField;
    }

    public EditText getSecondNumberField() {
        return secondNumberField;
    }

    public TextView getLastCalculationResult() {
        return lastCalculationResult;
    }

    public TextView getCalculationResult() {
        return calculationResult;
    }

}

Creating the Controller

To complete the MVC, define a controller which knows about both model and view. 

// CONTROLLER'S RESPONSABILITY: 
//
// MEDIATE USER'S ACTIONS BETWEEN MODEL AND VIEW. INTERACTS AS A DELEGATOR. 
//
// THIS IS THE CENTRAL PLACE TO LOOK FOR BEHAVIORS. EVERYTHING HAPPENS HERE...
public class CalculatorController extends Observable implements OnClickListener {

    private CalculatorModel model;
    private CalculatorView view;

    public CalculatorController(CalculatorModel model, CalculatorView view) {
        super();
        setModel(model);
        setView(view);
        bindControllerOnView();
        addObservers();
    }
    
    public void addObservers() {
        addObserver(new Calculator());
        addObserver(new Reseter());
    }
    
    private class Calculator implements Observer {
        @Override
        public void update(Observable observable, Object object) {
            if (R.id.calculateButton == ((View) object).getId()) {
                String first = getView().getFirstNumberField().getText().toString();
                String second = getView().getSecondNumberField().getText().toString();
                int firstNumber = Integer.valueOf(first);
                int secondNumber = Integer.valueOf(second);
                getModel().add2Numbers(firstNumber, secondNumber);
                int result = getModel().getActualResult();
                getView().getCalculationResult().setText(String.valueOf(result));
                getView().getLastCalculationResult().setText("Last Result: " + result);
            }
        }
    }

    private class Reseter implements Observer {
        @Override
        public void update(Observable observable, Object object) {
            if (R.id.resetButton == ((View) object).getId()) {
                getView().getCalculationResult().setText(String.valueOf(0));
                getView().getFirstNumberField().setText("");
                getView().getSecondNumberField().setText("");
            }
        }
    }

    @Override
    public void onClick(View view) {
        this.setChanged();
        this.notifyObservers(view);
    }

    public void setView(CalculatorView view) {this.view = view;}
    public CalculatorView getView() {return view;}
    public void setModel(CalculatorModel model) {this.model = model;}
    public CalculatorModel getModel() {return model;}
    public void bindControllerOnView(){getView().bind(this);}

    public void updateView() {/*Auto-generated method stub*/}
    public void updateDb() {/*Auto-generated method stub*/}

}

Using it in the CalculatorActivity

Finally call it in the activity. Take your time to understand it and see how small and understandable the class are. This approach also leads to SRP (single responsibility principle) which is desired and much better then having all havy weight code in one single class. 

The Usage

The final code looks like this:
 
// WHY WE DO THAT? BECAUSE CLEAN CODE MATTERS!!! ;)
//
// IMAGINE THIS CLASS AS THE CLIENT (THE MAIN CLASS)
// EACH ACTIVITY IS REPRESENTED BY A GROUP OF MVC'S
//
// IMPORTANT: USES THE CONTROLLER TO DELEGATE EVERYTHING. 
// IT DOESN'T CARE ABOUT THE IMPLEMENTATION.
//
public class CalculatorActivity extends Activity {
    
    protected CalculatorController controller;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        // MVC - APPLIED IN THE PRACTISE
        controller = new CalculatorController(new CalculatorModel(), new CalculatorView(this));
    }
    
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        // THIS IS JUST AN EXAMPLE, BUT YOU MAY DELEGATE THINGS ON:
        // onPause(), onResume(), onWhatEverYouNeed() ...
        //
        controller.updateView();
        controller.updateDb();
    }
}

Conclusions 

I found this way so nice and so clear, that i'm using it in my projects with a lot of possitive feedbacks. Maintenance has become a lot more easier and adding new features has also become a lot more clear and faster. Of course thats not the final approach. I did it that way here to make it simple and to show the idea and advantages behind it. In the final approach i have a defined a abstract model like this: public abstract class MvcModel implements Serializable..., an abstract generic Controller public abstract class MvcController< V extends MvcView, M extends MvcModel > extends Observable implements OnClickListener..., and an abstract view public abstract class MvcView.... Each of them contains a lot of utilities in it, making it simplier to me to implent my projects. So it is up to you to improve it and make it reusable for you. In fact i have created a lib project which i've placed all of those concepts i use.

Thats all. Hope you like it.

6 comments:

  1. I cringe a lot when reading articles about MVC. Not this time, much appreciation.

    Off topic: it came to my mind when reading the artice - in IDEs there should be two palettes. One for views and one for models. Both are independent, you can't use a model in a view and vice versa. When you write a costum view or model they'd automatically pop up in their palette. The app or lib you're programming is the controller. It's the glue code between them.

    To generalize this further. There could be pattern libraries to direct you. A pattern has a small set of arbitrary roles. You assign these roles to your classes. Then it should be easy to find wrong pattern-usages (not allowed dependencies) and inform you about the correct usage. Perhaps this generally is a little bit overkill, too restrictive. But it could be suitable to enforce MVC.

    ReplyDelete
  2. Interesting and helpful. But how to handle activity fragment dependencies with this kind of MVC?

    John

    ReplyDelete
    Replies
    1. Hi John, i didn't have an answer for you right now. I'm not facing this problem(need) at the moment. As soon as i do, i will post it here.

      Delete
  3. sir pls I can not understand this last part of your tutorial.  specily onActivityResult() function
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
            // THIS IS JUST AN EXAMPLE, BUT YOU MAY DELEGATE THINGS ON:
            // onPause(), onResume(), onWhatEverYouNeed() ...
            //
            controller.updateView();
            controller.updateDb();
        }

    ReplyDelete
    Replies
    1. hi Dr. Zohair Ahmed Faruqi! with this statement i mean: if your application needs, you could use the controller to call any other method you define by just overriding the onPause or onResume or whatever method you want. This way you would maintain the code clean and delegate responsibilities to the controller which decides if it should use the view or the model to do any computation for you.

      Delete