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
MVP architecture for Android
By Anas Ambri
MVP architecture for Android
Presentation given at AndroidMontreal Nov15
- 5,405