Porto

Blog

Clean Android (Part 1): MVP

In the Clean Android series we will descrive how to create clean Android applications using different software patterns and tools. This article will describe how to use the MVP (model-view-presenter) pattern. This is based and influenced by many other sites and blogs like Fernando Cejas Website.

You can download all the sources from GitHub.

What we what to achieve?

When creating Android applications Activities and Fragments tend to attract too many resposabilities, creating big classes difficult to rehuse. Using the MVP pattern we will separate the how to draw (view) from the what to do (Presenter).

Lets start coding

We will create a sample application, that will display cooking recipes. We will have two screens, one that will display a list of recipes, and another one with the detail.

The backend

To start with we need a dummy backend. A Recipe model:

public class Recipe {
    int id;
    String name;
    // constructor getter and setter (...)
}

And our dummy usecases.

/**
 * Recipe Dummy Use Cases
 */
public class RecipeUseCases {
    public static final List<Recipe> recipes = Arrays.asList(new Recipe[]{new Recipe(0, "Paella"), new Recipe(1, "Roasted Rabbit")});

    public Recipe getRecipe(int id){
        return recipes.get(id);
    }

    public List<Recipe> getRecipes(){
        return recipes;
    }
}

The Presenter

Lets define an interface for all our presenters. We want to react to resume, pause and destroy of the Activity (or Fragment).

public interface Presenter {
  void resume();
  void pause();
  void destroy();
}

And a default dummy implementation

/**
 * Presenter that does not respond to anything. Use as a base.
 */
public class BasePresenter implements Presenter {
    @Override
    public void resume() {}

    @Override
    public void pause() {}

    @Override
    public void destroy() {}
}

Now lets create the ListPresenter.

public class RecipeListPresenter  implements Presenter{
    ViewRenderer view;
    RecipeUseCases recipeUseCases = new RecipeUseCases();

    public void initialize(ViewRenderer view){
        this.view = view;
        loadRecipiesList();
    }

    private void loadRecipiesList(){
        view.render(recipeUseCases.getRecipes());
    }

    /**
     * Called by the view. It displays the recipie given.
     * @param recipe
     */
    public void displayRecipe(Recipe recipe){
        view.render(recipe);
    }

    @Override
    public void resume() {}

    @Override
    public void pause() {}

    @Override
    public void destroy() {}

    public interface ViewRenderer{
        /**
         * Renders the list to the screen
         * @param recipeList
         */
        public void render(List<Recipe> recipeList);

        /**
         * Renders a single recipie detail view (or performs navigation)
         * @param recipe
         */
        public void render(Recipe recipe);
    }
}
  1. We have defined an interface ViewRenderer that should be implemented by the Fragment that needs uses this Presenter.
  2. When initialized it will load the list of Recipies and tell the View to display them.
  3. When a recipie is clicked we will tell the View to show it.

Now, lets see the Show Recipe Presenter

public class ShowRecipeDetailPresenter implements Presenter{
    ViewRenderer view;
    int recipeId;
    RecipeUseCases recipeUseCases = new RecipeUseCases();


    public void initialize(ViewRenderer view, int recipeId){
        this.view = view;
        this.recipeId = recipeId;
        loadRecipe();
    }

    private void loadRecipe() {
        Recipe recipe = recipeUseCases.getRecipe(recipeId);
        view.render(recipe);
    }


    @Override
    public void resume() {}

    @Override
    public void pause() {}

    @Override
    public void destroy() {
    }

    public static interface ViewRenderer {
        public void render(Recipe recipie);
    }
}
  1. We defined an interface ViewRenderer that should be implemented by the Fragment that needs uses this Presenter.
  2. When initialized it will load the Recipie and tell the View to display it.

Simple, isn't it?

The View

Now we will create two fragments. Lets start with the ListFragment. It has to implement ViewRenderer of its Presenter.

public class ListRecipesFragment extends PresenterFragment implements RecipeListPresenter.ViewRenderer{
    @InjectView(R.id.list_view)
    ListView listView;

    RecipeListAdapter adapter;
    RecipeListPresenter presenter = new RecipeListPresenter();

    public static ListRecipesFragment getInstance(){
        return new ListRecipesFragment();
    }

    public ListRecipesFragment() {
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View rootView =  inflater.inflate(R.layout.recipes_list_layout, container, false);
        ButterKnife.inject(this, rootView);

        adapter = new RecipeListAdapter(getContext());
        listView.setAdapter(adapter);
        presenter.initialize(this);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                presenter.displayRecipe(adapter.getItem(position));
            }
        });

        return rootView;
    }

    @Override
    public void render(List<Recipe> recipieList) {
        adapter.addAll(recipieList);
    }

    @Override
    public void render(Recipe recipe) {
        new Navigator().show(getActivity(), recipe.getId());
    }

    @Override
    public Presenter getPresenter() {
        return presenter;
    }
}
  1. In the onCreateView we initialize the presenter.
  2. We define the two methods on the ViewRenderer.
  3. The Navigator class contains all the navigations of the application. We ommit its implemenation
  4. We also ommit RecipeListAdapter implementation. Its a standard ListAdapter.

The Show Recipe fragment will be the following:

public class ShowRecipeDetailFragment extends PresenterFragment implements ShowRecipeDetailPresenter.ViewRenderer {
    public static final String RECIPE_ID = "recipe_id";

    @InjectView(R.id.recipe_title)
    TextView recipeTitle;

    RecipeListAdapter adapter;
    ShowRecipeDetailPresenter presenter = new ShowRecipeDetailPresenter();

    public static ShowRecipeDetailFragment getInstance(int recipeId){
        ShowRecipeDetailFragment result = new ShowRecipeDetailFragment();
        Bundle bundle = new Bundle();
        bundle.putInt(RECIPE_ID, recipeId);
        result.setArguments(bundle);
        return result;
    }

    public ShowRecipeDetailFragment() {
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View rootView =  inflater.inflate(R.layout.recipe_detail_layout, container, false);
        ButterKnife.inject(this, rootView);

        int recipeId = getArguments().getInt(RECIPE_ID);

        presenter.initialize(this, recipeId);

        return rootView;
    }

    @Override
    public void render(Recipe recipe) {
        recipeTitle.setText(recipe.getName());
    }

    @Override
    public Presenter getPresenter() {
        return presenter;
    }
}

As you have seen the two Fragments inherit from PresenterFragment. We have connected the onPause, onResume, and onDestroy of the fragment to the Presenter there:

public abstract class PresenterFragment extends Fragment{
    public abstract Presenter getPresenter();

    @Override
    public void onPause() {
        getPresenter().pause();
        super.onPause();
    }

    @Override
    public void onResume() {
        super.onResume();
        getPresenter().resume();
    }

    @Override
    public void onDestroy() {
        getPresenter().destroy();
        super.onDestroy();
    }
}

That's all for today!

You can continue with the Clean Android series on the following article Clean Android (Part 2): MVP Forms