Mobile applications, though often times limited in scope and size, can still grow rapidly to a fairly high degree of complexity. In this series of blog posts I will demonstrate how to manage that complexity and how to design an app architecture that will make the app more easily testable and robust. To make the learning process easier, I will link to a sample project in each post that will contain all of the code that has been worked out in the course of the blog post. The sample code is a functional Android Studio project which you should be able to download and run without too many issues. In this first post I will show you how I applied the model-view-presenter pattern to separate the Android code from the rest of the java code and thus make the app’s code more cohesive and more easily testable.
The sample code for this post can be found here.
We start by creating a new project. The name of the project is Songster. It lets you search for songs on a music service and bookmark them. Songster will consist mainly of one screen, the search screen. The search screen allows the user to search for songs and bookmark them. Bookmarked songs display a check mark or some other icon next to them to indicate that they’ve been bookmarked. We will place this search screen inside a view pager so that we could potentially add another screen later that the user could reach by swiping horizontally. This is just a sample app and does not embody the best practices in user interface design. Let’s set up all the necessary boilerplate. First our activity:
MainActivity
The MainActivity hosts our view pager which will display the search screen.
private ViewPager mViewPager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mViewPager = (ViewPager) findViewById(R.id.viewPager); mViewPager.setAdapter(new MainPagerAdapter(getSupportFragmentManager())); }
We bind our view pager instance to the inflated view and set its adapter.
MainPagerAdapter
The adapter currently only displays the SearchFragment. We could add additional fragments later.
public class MainPagerAdapter extends FragmentStatePagerAdapter { public MainPagerAdapter(FragmentManager fm) { super(fm); } @Override public Fragment getItem(int position) { return SearchFragment.getInstance(); } @Override public int getCount() { return 1; } }
The SearchFragment
This is where it starts to get a bit more interesting. The SearchFragment allows the user to search for songs in the music service. We could put all of the code to handle this logic into the fragment. That is the most straight forward approach. However, I would like to show you the benefits of separating the Android-specific code from the business logic. To achieve this, we will use the Model-View-Presenter pattern. The following diagram demonstrates the model-view-presenter pattern that is used in Songster to create a nice separation of the Android and the non-Android domain.
In code this looks as follows. In the SearchFragment we instantiate an instance of the SearchPresenter in the Fragment’s onCreate() method and pass the Fragment in as the presenter’s view:
mPresenter = new SearchPresenter(new SearchServiceRepository(), this);
The first argument is the implementation of the repository that deals with communicating with the backend service. The second argument is an implementation of the SearchView interface. We now propagate each view event to the presenter. An example of this can be seen in the search field’s EditorActionListener:
mSearchEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (actionId == EditorInfo.IME_ACTION_SEARCH) { mPresenter.onSearch(mSearchEditText.getText().toString()); return true; } return false; } });
This handler listens for when the user presses the search button on the keyboard after entering a text into the search field. When the search button is pressed, we call the presenter’s onSearch() method and pass in the text that’s currently in the search field. The presenter then performs all the logic behind a search call and calls various methods of the SearchView where necessary. This approach allows us to isolate all of the Android-specific boilerplate to the Android classes (in this case our Fragment) and to put all of the logic into the presenter.
The SearchPresenter
The SearchPresenter contains the bulk of the logic associated with searching. It has a reference to the SearchView which allows it to display changes in the UI. It’s constructor looks like this:
public SearchPresenter(SearchRepository searchRepository, SearchView searchView){ mSearchRepository = searchRepository; mSearchView = searchView; }
We simply assign the passed in values to fields. The references passed in could very well also be injected by an IoC container. In the various methods in the presenter, we perform our logic and update the UI. The onSearch() method from earlier could look something like this:
public void onSearch(String searchString) { if(searchString != null && !searchString.isEmpty()){ mSearchView.showProgressBar(); mSearchView.hideKeyboard(); mSearchRepository.search(searchString, this); } }
We first display a progress bar in the UI, hide the keyboard and use the repository to perform a search. The presenter does not have to know anything about how to display a progress bar, it simply knows that it wants to display one. The same goes for hiding the keyboard. The implementations of the SearchView methods are in our Fragment and could look like this:
@Override public void showProgressBar() { mProgressBar.setVisibility(View.VISIBLE); } @Override public void hideKeyboard() { if(isAdded() && getActivity() != null) { // Check if no view has focus View view = getActivity().getCurrentFocus(); if (view != null) { InputMethodManager imm = (InputMethodManager) getActivity(). getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(view.getWindowToken(), 0); } } }
The beauty of this approach is that it makes the logic in our presenter much easier to read. Would we perform all of the logic directly in the fragment, the onSearch() method might look something like this:
public void onSearch(String searchString) { if(searchString != null && !searchString.isEmpty()){ mProgressBar.setVisibility(View.VISIBLE); // Check if no view has focus View view = getActivity().getCurrentFocus(); if (view != null) { InputMethodManager imm = (InputMethodManager)getActivity(). getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(view.getWindowToken(), 0); } mSearchRepository.search(searchString, this); } }
You could extract the individual parts of the methods into separate private methods within the fragment and thus improve the readability. There are additional benefits to having the logic in the presenter, however. One is that we adhere more strongly to the single responsibility principle by making the presenter responsible for the logic and the Fragment for the Android-specific boilerplate. Furthermore, it is very easy to unit test our presenter without the use of a testing framework such as Robolectric. We can simply mock our SearchView interface and verify that its methods are called. We can create the following unit tests for the onSearch() method with relative ease:
@Test public void testOnSearch_when_searchString_then_showProgressBar() { SearchView mockView = mock(SearchView.class); SearchPresenter testee = new SearchPresenter(mock(SearchRepository.class), mockView); testee.onSearch("Some string"); verify(mockView).showProgressBar(); } @Test public void testOnSearch_when_searchString_then_hideKeyboard() { SearchView mockView = mock(SearchView.class); SearchPresenter testee = new SearchPresenter(mock(SearchRepository.class), mockView); testee.onSearch("Some string"); verify(mockView).hideKeyboard(); }
These tests are short, to the point and easy to maintain. Yet they still test relevant behaviour of the onSearch() method.
Conclusion
The model-view-presenter pattern in Android can greatly contribute to the separation of concerns in an Android application. It does so by separating the Android-specific code from our app logic. Furthermore, it makes an app much easier to unit test. This pattern can still be improved, however. One thing that is missing from our presenter is the maintaining of the view’s state. In the next post I will introduce a view model to deal with maintaining a view’s state.
Sources:
Architecting Android… The clean way?
Hi Chris
Thanks for the post. Definitely a hot topic at the moment the MVP stuff !
Cheers
Simon
Indeed it is. I’m looking into data binding on Android at the moment and how it can be used to implement an MVVM pattern. Also pretty interesting stuff :).
Pingback: Noser Blog Data binding and Model-View-ViewModel in Android - Noser Blog