MVP Architecture for Android

Anas Ambri

Android Montreal Meetup - November 2015

$whoami

Android & iOS dev @ Guaraná

@AnasAmbri

This plug has no shame

At Guaraná, we make Android & iOS apps

We are also hiring!

Outline

  • Why care about Architecture in Android?
  • Why to MVP on Android?
  • How to MVP on Android?
  • Pros/Cons to using MVP

Architecture on Android

2015: Year of Architecture on Android

  • October 2014: Square against Fragments [1]
  • December 2014: Dagger 2.0
  • March 2015: Mosby [2] & Nucleus [3] released
  • May 2015: VIPER architecture in Coursera app [4]
  • May 2015: Data-binding library announced
  • August 2015: RxAndroid gets 1.0 release

Trends

(Predicted) Trends

Summary

  • Platform maturing

  • Community interest increasing

  • Architecture can actually solve problems

Why use MVP?

Biggest problem on Android

Testing

Integration tests: the one your client cares about

Why is testing UI so complicated?

UI is very coupled to Android framework

           You can't instantiate Activity, the system does it for you

public static void main(String [] args) {
    HomeActivity activity = new HomeActivity();
    activity.onCreate();
    activity.onStart();
    activity.onResume();
    assertNotNull("Activity was null", activity);
}

Testing UI in the JVM is not possible

Why is testing UI so complicated?

@RunWith(AndroidJUnit4.class)
public class ExampleTest 
       extends ActivityInstrumentationTestCase2<HomeActivity> {
    HomeActivity mActivity; 

    public ExampleTest() { super(HomeActivity.class); }  

    @Before
    public void setUp() throws Exception {
        super.setUp();
        mActivity = getActivity();
    }

    @Test
    public void testPreconditions() { 
        assertNotNull("Activity was null", mActivity); 
    }
}

How bad is it?

  • Setting up tests on an existing project is a nightmare[5]
  • Flaky adb is flaky
  • ActivityInstrumentationTestCase2
  • Running tests on build machine?
  • No TDD
  • Google's own IO app didn't have tests [6]

How bad is it?

Congratulations, by complaining about testing support you're now one step closer to becoming a true Android dev  

/u/RattlesnakeSpeedway [4]

Light at the end of tunnel

MVP to the rescue

Solution

  • Decouple the state of the UI from the UI
    • Move it somewhere where we can control it
    • Instantiate it, mock it, inject it
    • Basically, make the state into a POJO

Solution in code

public class HomeActivity extends Activity {
    private HomeState state;
    private Button button;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        //setup
        state = new HomeState();
        button.setOnClickListener(new View.OnClickListener() {
            @Override 
            public void onClick(View v) { state.buttonClicked(); }
        });
    }
}

public class HomeState {
    public buttonClicked() {
        //Decide what to do with the button click
    }
}

Expanded Solution

public class HomeState {
    private HomeActivity activity;
    public HomeState(HomeActivity activity) { this.activity = activity; }

    public buttonClicked() { this.activity.populateWithData(new String {"Item"}); }
}

public class HomeActivity extends Activity {

    private HomeState state;
    private Button button;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        //setup layout, etc.
        state = new HomeState(this);
        //Button click listener
    }

    public void populateWithData(String [] items) {
         //Populate
    }
}

Improving the solution

public class HomePresenter {

    private WeakReference<HomeView> view;

    public HomePresenter(HomeView v) { 
        this.view = new WeakReference(v); 
    }

    public buttonClicked() { 
        ((HomeView)view).get().populateWithData(new String {"Item"}); 
    }
}

public interface HomeView {
    void populateWithData(String [] items);
}

Improving the solution

public class HomeActivity extends Activity implements HomeView {
    private HomePresenter presenter;
    private Button button;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        //setup layout, etc.
        presenter = new HomePresenter(this);
        button.setOnClickListener(new View.OnClickListener() {
            //Handle button click
        });
    }

    @Override
    public void populateWithData(String [] items) {
         //Populate
    }    
}

Summary

  • Presenter does not know anything about Android
    • It only cares about the View Interface
  • Any event from the view (Click, Resume) that affects the internal state is forwarded to the presenter
  • Any object that implements the View interface can create the presenter 
    • Can be Activity, Fragment, ViewGroup, etc.

Can we test now?

public static void main(String [] args) {
    TestHomeView view = new TestHomeView();
    HomePresenter presenter = new HomePresenter(view);
    presenter.buttonClicked();
    assertEquals(1, view.getItems().size()); //Tests all the logic
}

public class TestHomeView implements HomeView {

    private List<String> items = new ArrayList();

    @Override
    public void populateWithData(String[] items) {
        this.items = new ArrayList(Arrays.asList(items));
    }
}

Even better summary

How to use MVP?

Roll out your own

Many people have been doing MVP for some time now on Android. 

 

You can too

Things to consider

Follow Martin Fowler's advice

  • Supervising Presenter[7]
  • Presentation Model[8]
  • Passive View[9]

Supervising Presenter

Presenter has two responsibilities:

  • "Input response"
  • "Partial View/model synchronization"

 

 

Partial because data-binding takes care of simple synchronization

Presentation Model

Model is no longer dumb. 

  • This model  =/= backend Model
  • Presenter might need to create it
  • Presentation Model is coupled to View

(Thank god Google came up with that data binding library)

Passive View

You want to keep the view as passive as possible:

  • This means it never updates itself 
  • You can then easily convince yourself not to test it

Extra Things to consider on Android

  • Navigation & Deeplinking
  • Orientation changes

Navigation & Deeplinking

This is the view's responsibility

  • Since we used Activity/Fragment/ViewGroup, this only seems natural
  • The decision to move to another Activity is still the presenter's job

Orientation Changes

Your app can be killed by Ninjas the OS at any time

  • We need to save the state of the view and restore it
  • Use IcePick[10]

Orientation Changes

public class HomeViewState {
    
    private final int LOADING = 0;
    private final int SHOWING_ITEMS = 1;

    private int state = SHOWING_ITEMS;

    public void setLoading() {
        this.state = LOADING;
    }

    public void applyState(HomeView v) {
        if(state == LOADING) {
            view.showLoading();
        }
        //Handle other states
    }
}

Orientation Changes

class HomeActivity implements HomeView {
  @State HomeViewState state;

  @Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Icepick.restoreInstanceState(this, savedInstanceState);
  }

  @Override public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    Icepick.saveInstanceState(this, outState);
  }
}

Existing solutions

  • Mosby [2]
  • Nucleus [3]

 

Pretty sure there are plenty others

Demo

FAQs

FAQs

Should you use Activity, Fragment or ViewGroup?

 

It depends. This can work with any of the three. 

FAQs

Do you keep only one presenter at a time?

 

You have as many presenters as views. You can combine them, but why not KISS?

 

Taken from [2]

FAQs

How to ensure Presenter is a POJO?

 

Never import anything from com.android.* in your Presenters

FAQs

What about MVVM?

 

 

Yeah, what about it?

FAQs

Can you combine MVVM with MVP?

 

Maybe not combine (MPVMM is a mouth-full), but use data-binding, yes

FAQs

Can you combine this with other strategies?

 

  • RxAndroid/EventBus works pretty well to inform presenter.
  • Dagger to inject Presenters into Views

FAQs

Should I tell my boss about this?

 

Absolutely. Do you think that Google trends line is gonna go up by itself?

Resources

[1]: https://corner.squareup.com/2014/10/advocating-against-android-fragments.html

[2]: http://hannesdorfmann.com/android/mosby

[3]: https://github.com/konmik/nucleus

[4]: https://speakerdeck.com/richk/effective-android-architecture

[5]: https://www.reddit.com/r/androiddev/comments/39h62v/why_is_testing_so_broken_on_android/

[6]: https://github.com/google/iosched

[7]: http://martinfowler.com/eaaDev/SupervisingPresenter.html

[8]: http://martinfowler.com/eaaDev/PresentationModel.html

[9]: http://martinfowler.com/eaaDev/PassiveView.html

[10]: https://github.com/frankiesardo/icepick

Thank you!

If you like memes, and hate the state of Android testing, share these : http://verybadalloc.com/testing-memes

Would love to hear your feedback @AnasAmbri