Go Back

MVP on Android

Model View Presenter in Android

Model View Presenter in Android

 

I’m meddling with android programming now and I’ve found MVP is a great way to decouple presentation to allow for unit testing so I wanted to immediately get that practice down. Here is what I picked up.

 

First, let’s create a project. I name the project MvpExample, choose a target platform of 2.1 (that’s what my phone has). Package name is like a namespace. Activity from what I can tell is the first view that your app opens up to. I named my AskNameView because I plan on asking the user for their name and age and then displaying it on the screen.

 

 

It asks me for a unit test project. I am on the fence whether I like unit tests in the same project or not but in this case I don’t know enough to argue with the wizard J

 

 

Let’s go into the test project and create an AskNamePresenterTests code file to start working on as we do TDD.

 

 

 

Make a new class inheriting from TestCase

 

 

 

Now this may seem like commonsense to you java developers but it took me a while to figure out there is a naming convention here required to do a test. Every test method must start with ‘test’. Annoying in my opinion because that’s 4 more letters I could use for a well named test. Here’s my first ‘always pass’ test to make sure I understood JUnit.

 

package com.jpeckham.examples.test;

 

import junit.framework.TestCase;

 

public class AskNamePresenterTests extends TestCase {

 

      public void testFirst() {

            Boolean expected = true, actual = true;

           

            assertEquals(expected, actual);          

      }

}

 

So we continue on now. Let’s create our first presenter test for real.

 

I write the test first without any supporting code. It looks like this:

      public void testFirst() {

            TestingAskNameView view = new TestingAskNameView();

            AskNamePresenter presenter = new AskNamePresenter(view);

                       

            presenter.EnterNameAndAge("James",25);//hah 25 :)

           

            assertAreEqual("James is 25 years old",

                        view.getTextShownToUser());

      }

 

I figure I’ll wait to learn mocks until another day so I’m hard coding a fake view to work with here. The view will show “James is 25 year old” on it.

 

Of course none of this builds yet because the classes don’t exist anywhere.

 

Let’s start by making our TestingAskNameView class. Go to create new class on the TEST project. I created a new ‘fakes’ package (sub namespace)

 

 

Note: for you MVP gurus don’t worry that I haven’t inherited from anything yet or implemented a view because I haven’t created one yet.

 

Now if you go back to the test file and hover over the declaration of our local variable for the testing view you can see a  ‘quick fix’ that asks to import our new package “fakes”. Go ahead and do that.

 

All the code looks like this now in the tests:

package com.jpeckham.examples.test;

 

import com.jpeckham.examples.test.fakes.TestingAskNameView;

 

import junit.framework.TestCase;

 

public class AskNamePresenterTests extends TestCase {

 

      public void testFirst() {

            TestingAskNameView view = new TestingAskNameView();

            AskNamePresenter presenter = new AskNamePresenter(view);

                       

            presenter.EnterNameAndAge("James",25);//hah 25 :)

           

            assertAreEqual("James is 25 years old",

                        view.getTextShownToUser());

      }

}

 

K, now I’m going to hammer out the presenter. Here’s the presenter code. Note it needs an instance of the view as a member variable (or some people call field). (not sure what they call it in java… class variable?) . Note this one is going into the android project.

 

I do a little boilerplate mvp code:

package com.jpeckham.examples.presenters;

 

public class AskNamePresenter {

      private IAskNameView _view;

      public AskNamePresenter(IAskNameView view){

            _view = view;

      }

}

 

Now I use the IDE to generate the IAskNameView interface (new sub package)

 

 

I’ll move a little quicker now. Here’s the interface code:

package com.jpeckham.examples.views;

 

public interface IAskNameView {

      public void setDisplayedText(String text);

}

 

Now go over to the android activity class  (the concrete view)

package com.jpeckham.examples;

 

import com.jpeckham.examples.views.IAskNameView;

 

import android.app.Activity;

import android.os.Bundle;

 

public class AskNameView extends Activity implements IAskNameView {

    /** Called when the activity is first created. */

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

    }

 

      @Override

      public void setDisplayedText(String text) {

            // TODO Auto-generated method stub

           

      }

}

 

A few things to click on and generate here. (Don’t wire up anything yet we have some changes to do here in a few… we’re still working on making the presenter work)

 

Now implement the view in the fake (over in the test project)

package com.jpeckham.examples.test.fakes;

 

import com.jpeckham.examples.views.IAskNameView;

 

public class TestingAskNameView implements IAskNameView {

      private String _displayedText;

     

      public String getTextShownToUser(){

            return _displayedText;

      }

      @Override

      public void setDisplayedText(String text) {

            _displayedText = text;       

      }

 

}

 

Ok let’s see how we’re doing in the test project again… looks like the Presenter package needs imported.

package com.jpeckham.examples.test;

 

import com.jpeckham.examples.presenters.AskNamePresenter;

import com.jpeckham.examples.test.fakes.TestingAskNameView;

 

import junit.framework.TestCase;

 

public class AskNamePresenterTests extends TestCase {

 

      public void testFirst() {

            TestingAskNameView view = new TestingAskNameView();

            AskNamePresenter presenter = new AskNamePresenter(view);

                       

            presenter.EnterNameAndAge("James",25);//hah 25 :)

           

            assertAreEqual("James is 25 years old",

                        view.getTextShownToUser());

      }

}

Now we need the EnterNameAndAge method stubbed in.

public class AskNamePresenter {

      private IAskNameView _view;

      public AskNamePresenter(IAskNameView view){

            _view = view;

      }

      public void EnterNameAndAge(String string, int age) {

            // TODO Auto-generated method stub

           

      }

}

 

And a java rookie mistake I had assertAreEqual… let’s change that

      public void testFirst() {

            TestingAskNameView view = new TestingAskNameView();

            AskNamePresenter presenter = new AskNamePresenter(view);

                       

            presenter.EnterNameAndAge("James",25);//hah 25 :)

           

            assertEquals("James is 25 years old",

                        view.getTextShownToUser());

      }

 

Now we have a failing test. In TDD with MVP that means you’re about 90% done because all of the architecture, design, and mvp boilerplate stuff is what takes the most ‘work’.

 

 

 

Alrighty, now let’s make it pass. (ignore the duplication we’ll refactor…)

      public void EnterNameAndAge(String string, int age) {

            _view.setDisplayedText("James is 25 years old");

      }

 

Ahh that’s nice:

 

 

Ok now refactor out the hard coded values (and I fixed the param name)

      public void EnterNameAndAge(String name, int age) {

            _view.setDisplayedText(String.format("%s is %d years old",name,age));

      }

 

 

Ok now the ‘easy’ part … design a droid UI and wire up the event.

 

Let’s do a linear layout like this in our \res\layout\main.xml:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:orientation="vertical"

    android:layout_width="fill_parent"

    android:layout_height="fill_parent"

    >

<TextView 

    android:layout_width="fill_parent"

    android:layout_height="wrap_content"

    android:text=""

    android:id="@+id/shown"

    />

<TextView 

    android:layout_width="fill_parent"

    android:layout_height="wrap_content"

    android:text="Name:"

    />

<EditText

      android:singleLine="True"

      android:layout_width="fill_parent"

    android:layout_height="40px"

    android:id="@+id/name"

    />

<TextView 

    android:layout_width="fill_parent"

    android:layout_height="wrap_content"

    android:text="Age:"

    />

<EditText

      android:singleLine="True"

      android:layout_width="fill_parent"

    android:layout_height="40px"

    android:id="@+id/age"

    />

<Button

      android:layout_width="fill_parent"

      android:layout_height="wrap_content"

      android:text="Submit"

      android:onClick="submitClicked"

      />

</LinearLayout>

 

 

And then finally hook up everything in the concrete view like this:

package com.jpeckham.examples;

 

import com.jpeckham.examples.presenters.AskNamePresenter;

import com.jpeckham.examples.views.IAskNameView;

 

import android.app.Activity;

import android.os.Bundle;

import android.view.View;

import android.widget.*;

 

public class AskNameView extends Activity implements IAskNameView {

    /** Called when the activity is first created. */

      AskNamePresenter _presenter;

     

      public AskNameView(){

            _presenter = new AskNamePresenter(this);

      }

     

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

    }

 

      @Override

      public void setDisplayedText(String text) {

            getShownLabel().setText(text);

      }

     

      public void submitClicked(View widget)    {

            _presenter.EnterNameAndAge(getNameField().getText().toString(),

                        Integer.parseInt(getAgeField().getText().toString()));

      }

     

      private EditText getNameField()     {

            return (EditText) findViewById(R.id.name);

      }

      private EditText getAgeField(){

            return (EditText) findViewById(R.id.age);

      }

      private TextView getShownLabel(){

            return (TextView) findViewById(R.id.shown);

      }

}

 

 

Then when you run as droid app… you’re up and running (if you can figure out how to turn yours to English unlike me …)

 

 

 

 

 

 

 

 

CLICK for source code

 

disclaimer: i know nothing about packaging java stuff so don't flame me on how i zipped this or if it doesn't work when you unzip it.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Facebook DZone It! Digg It! StumbleUpon Technorati Del.icio.us NewsVine Reddit Blinklist Furl it!