Porto

Blog

Clean Android (Part 2): MVP edit and create screens

We will update the CleanAndroid application and we will add 2 screens. One to create and one to edit recipes. This article is a continuation of the post Clean Android (Part 1): MVP.

You can download all the sources from GitHub Project.

The business logic

Let's first update the dummy usecases. We need to add:

    public Recipe create(Recipe recipe){
        recipe.setId(recipes.size());
        recipes.add(recipe);
        return recipe;
    }

    public Recipe update(Recipe recipe){
        recipes.set(recipe.getId(), recipe);
        return recipe;
    }

This data is just stored into an static variable and changes will be erased. However, as we are centered in the view, it will work out for our example.

The presenters

We will need two presenters. We start by the CreateRecipePresenter.

public class CreateRecipePresenter extends BasePresenter {
    ViewRenderer view;

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

    /**
     * Called by the view to save the recipe.
     * @param name recipe name
     */
    public void saveRecipe(String name){
        Recipe created = recipeUseCases.create(new Recipe(name));
        view.recipeCreated(created);
    }

    public interface ViewRenderer{
        public void recipeCreated(Recipe recipe);
    }
}

And the UpdateRecipePresenter. It requires a view that renders the recipe (in edit mode) and that reacts when the recipe has been updated.

public class UpdateRecipePresenter extends BasePresenter{
    ViewRenderer view;
    Recipe recipe;

    public void initialize(ViewRenderer view, int recipeId){
        this.view = view;
        recipe = getRecipeUseCases().getRecipe(recipeId);
        view.render(recipe);
    }

    /**
     * Called by the view to save the recipe.
     * @param name recipe name
     */
    public void saveRecipe(String name){
        recipe.setName(name);
        recipe = getRecipeUseCases().update(recipe);
        view.recipeUpdated(recipe);
    }

    public interface ViewRenderer {
        /**
         * Renders the recipe to the view for edit
         */
        public void render(Recipe recipe);

        /**
         * Executed when the recipe modifications have been stored.
         */
        public void recipeUpdated(Recipe recipe);
    }
}

Decisions done

When defining the method saveRecipe we have faced different options:

  1. saveRecipe(String name) the view sends the values of the attributes of a recipe.
  2. saveRecipe(Recipe recipe) the view sends a recipe with its values
  3. saveRecipe(RecipeViewModel recipe) the view sends a class with the values of the recipe.

Each of the options has his advantages and disadvantages. In most similar places like to use the saveRecipe(Recipe recipe) because is the one that rehuses more code, however, in the EditView I didn't want the view to keep track of the Recipe (I wanted to store it in the Presenter). Therefore if've decided to go with saveRecipe(String name). I discarted the third option because you need to create a separate model and convertions.

The views

We will need to create two new fragments. The CreateRecipeFragment.

public class CreateRecipeFragment extends BaseFragment<CreateRecipePresenter> implements CreateRecipePresenter.ViewRenderer{
    public static CreateRecipeFragment getInstance(){
        return new CreateRecipeFragment();
    }

    CreateRecipePresenter presenter = new CreateRecipePresenter();

    @InjectView(R.id.recipe_name)
    EditText recipeName;

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

        return rootView;
    }

    @OnClick(R.id.save_button)
    public void saveRecipe(){
        String name = recipeName.getText().toString();
        presenter.saveRecipe(name);
    }

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

    @Override
    public void recipeCreated(Recipe recipe) {
        getNavigator().listRecipes(getActivity());
    }
}

And the UpdateRecipeFragment

public class UpdateRecipeFragment extends BaseFragment<UpdateRecipePresenter> implements UpdateRecipePresenter.ViewRenderer{
    public static final String RECIPE_ID = "recipe_id";

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

    UpdateRecipePresenter presenter = new UpdateRecipePresenter();

    @InjectView(R.id.recipe_name)
    EditText recipeName;

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

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

        presenter.initialize(this, recipeId);

        return rootView;
    }

    @OnClick(R.id.save_button)
    public void saveRecipe(){
        String name = recipeName.getText().toString();
        presenter.saveRecipe(name);
    }

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

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

    @Override
    public void recipeUpdated(Recipe recipe) {
        getNavigator().listRecipes(getActivity());
    }
}

Final changes

We will need to change the existing views to add navigation the the edit and create fragments. You can check the canges at the GitHub project.

Other improvements done

A part from adding this new screens we have made

  1. We have modified the generic PresenterFragment and added a type

    that represents the Presenter class.

  2. We have renamed the method RecipeListPresenter#recipeClicked(Recipe recipe) to displayRecipe(Recipe recipe). The idea is that the View can change but the presenter remains the same. However the concept of UserClicked is something more of the view. Maybe the view changes and the user is displayed in a double click. Therefore we hace rename the method to displayRecipe (note: 1st post has been updated).

Thants all. Thanks for reading