MVP and MVC

This is the second post in series of three posts that discuss Model View Controller (MVC) and Model View Presenter (MVP) architectural patterns in context of Android development (part 1, part 3).

MVP or MVC:

In part 1 of this post we concluded that MVP architectural pattern is more suitable for Android development due to very tight coupling between Activities and Fragments (which are MVP presenters) and various parts of Android framework.

However, since the terms MVP and MVC are ambiguous, many programmers don’t bother to make a distinction between the two architectural patterns. I tend to agree with those who discard the differences. At the end of a day, without additional explanations, the statement “my application uses MVP” conveys exactly the same information as the statement “my application uses MVC”

The above means that we can choose either MVP or MVC as the “representative” name for both of the patterns. Which one should we use? I think that the term “controller” is more descriptive than “presenter”. Furthermore, outside of Android community, MVC is used more widely than MVP. Therefore, starting now, I will designate the architectural pattern that we are implementing MVC.

As I said earlier, there is no special complexity associated with implementation of model in Android. You can write your own ContentProvider, or, if it is too complex for your needs, you can implement a standalone application’s model (using e.g. Singleton design pattern injected implementation of some ModelMvc interface). Since there is nothing special about models in Android, in the tutorial application I decided to use an existing external ContentProvider as a model in order to be able to concentrate on the tricky parts of MVC in Android. If you need a good tutorial on ContentProviders – Wolfram Rittmeyer wrote a great series of posts about it.

Now, you might be wondering whether Android View class has anything to do with MVC views, so let’s begin by making our terminology unambiguous.

Difference between MVC view and Android View:

Android framework already contains a component called View – it is a base class for all UI building blocks. While there are some similarities between Android Views and MVC views, they are not the same. The relationship between the two can be described this way:

  • In general, Android View is not MVC view.
  • For each MVC view, there must be a single Android View which represents MVC view’s “user visible” part (the “root” Android View of UI hierarchy).
  • MVC view can contain nested MVC views.

It might be a bit confusing right now, but the examples provided later will put the above statements into meaningful context.

Since we got this ambiguity in respect to the meaning of “view”, I shall explicitly mention the type of “view” I’m referring to – it will be either “Android View”, or “MVC view”. Keep in mind that when I say Android View, I mean instances of either View class itself, or any of its sub-classes (widgets, layouts, etc.).

MVC view implementation:

So, how do we define MVC view? In the tutorial app, MVC view is any class which implements ViewMvc interface (the javadoc is self explanatory):

/**
 * MVC view interface.
 * MVC view is a "dumb" component used for presenting information to the user.<br>
 * Please note that MVC view is not the same as Android View - MVC view will usually wrap one or
 * more Android View's while adding logic for communication with MVC Controller.
 */
public interface ViewMvc {

    /**
     * Get the root Android View which is used internally by this MVC View for presenting data
     * to the user.<br>
     * The returned Android View might be used by an MVC Controller in order to query or alter the
     * properties of either the root Android View itself, or any of its child Android View's.
     * @return root Android View of this MVC View
     */
    public View getRootView();

    /**
     * This method aggregates all the information about the state of this MVC View into Bundle
     * object. The keys in the returned Bundle must be provided as public constants inside the
     * interfaces (or implementations if no interface defined) of concrete MVC views.<br>
     * The main use case for this method is exporting the state of editable Android Views underlying
     * the MVC view. This information can be used by MVC controller for e.g. processing user's
     * input or saving view's state during lifecycle events.
     * @return Bundle containing the state of this MVC View, or null if the view has no state
     */
    public Bundle getViewState();

}

Note that this interface is very general and lightweight, therefore any existing class can be easily extended or wrapped in order to be used as MVC view. On the other hand, this interface does not have any means for differentiating between different MVC views – each concrete MVC view will need to be defined as sub-interface of ViewMvc, and MVC controllers will depend on these sub-interfaces. This dependency does not violate MVC paradigms – controllers need to depend on particular MVC views, though this dependency should be constrained to dependency on just the interface.

Let’s take a closer look at the interface of one of the MVC views in the tutorial appSmsDetailsViewMvc:

/**
 * This interface corresponds to "details" screen of the app, where details of a single SMS
 * message should be displayed
 */
public interface SmsDetailsViewMvc extends ViewMvc {

    interface ShowDetailsViewMvcListener {
        /**
         * This callback will be invoked when "mark as read" button is being clicked
         */
        void onMarkAsReadClick();
    }

    /**
     * Hide "mark as read" button
     */
    void markAsReadNotSupported();

    /**
     * Show details of a particular SMS message
     * @param smsMessage a message to show
     */
    void bindSmsMessage(SmsMessage smsMessage);


    /**
     * Set a listener that will be notified by this MVC view
     * @param listener listener that should be notified; null to clear
     */
    void setListener(ShowDetailsViewMvcListener listener);

}

Please note that just by looking at this interface we can immediately know what this MVC view does. This is the power of a good abstraction – it hides implementation details and allows us to understand the WHAT while safely ignoring the HOW.

The above abstraction is very important for long term maintainability. Think A/B testing: with this abstraction in place, all you need to do is provide several implementations of this interface. Think UI changes: with this abstraction in place, these changes will be isolated as long as the interface remains stable. Think business rules changes: with this abstraction in place, you can safely ignore all the UI logic, and only know about this interface.

I will even go as far as saying that this abstraction is the most important feature of MVC.

The actual functional class that implements SmsDetailsViewMvc interface is SmsDetailsViewMvcImpl:

/**
 * An implementation of {@link SmsDetailsViewMvc} interface
 */
public class SmsDetailsViewMvcImpl implements SmsDetailsViewMvc {

    private View mRootView;
    private ShowDetailsViewMvcListener mListener;
    private boolean mMarkAsReadSupported = true;

    private TextView mTxtAddress;
    private TextView mTxtDate;
    private TextView mTxtBody;
    private Button mBtnMarkAsRead;

    public SmsDetailsViewMvcImpl(LayoutInflater inflater, ViewGroup container) {
        mRootView = inflater.inflate(R.layout.mvc_view_sms_details, container, false);

        initialize();

        mBtnMarkAsRead.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (mListener != null) {
                    mListener.onMarkAsReadClick();
                }
            }
        });
    }

    private void initialize() {
        mTxtAddress = (TextView) mRootView.findViewById(R.id.txt_sms_address);
        mTxtDate = (TextView) mRootView.findViewById(R.id.txt_sms_date);
        mTxtBody = (TextView) mRootView.findViewById(R.id.txt_sms_body);
        mBtnMarkAsRead = (Button) mRootView.findViewById(R.id.btn_mark_as_read);
    }

    @Override
    public void markAsReadNotSupported() {
        mMarkAsReadSupported = false;
    }

    @Override
    public void bindSmsMessage(SmsMessage smsMessage) {
        mTxtAddress.setText(smsMessage.getAddress());
        mTxtDate.setText(smsMessage.getDate());
        mTxtBody.setText(smsMessage.getBody());

        mRootView.setBackgroundColor(smsMessage.isUnread() ?
                mRootView.getResources().getColor(android.R.color.holo_green_light) :
                mRootView.getResources().getColor(android.R.color.white));

        mBtnMarkAsRead.setVisibility(smsMessage.isUnread() && mMarkAsReadSupported ?
                View.VISIBLE : View.GONE);

    }

    @Override
    public void setListener(ShowDetailsViewMvcListener listener) {
        mListener = listener;
    }

    @Override
    public View getRootView() {
        return mRootView;
    }

    @Override
    public Bundle getViewState() {
        return null;
    }

}

As you can see, this MVC view is very simple, which is always an advantage. Let’s understand what’s going on here:

  • Constructor: the main aspect of MVC views’s construction is initialization of underlying Android “root” View. Usually you will do that by inflating it from XML layout file. Once root Android View is initialized, you will proceed with initialization of other Android Views contained in the inflated hierarchy. In this case, we simply obtained references to hierarchical Android Views that we will use, and registered a click listener with one of them.
  • getViewState(): this MVC view does not capture “stateful” user’s input (button clicks are not “stateful”), therefore it does not have any state that MVC controllers could be interested in. In such cases, getViewState() method should return null.
  • bindSmsMessage(SmsMessage): this MVC view presents data to the user. It is controller’s responsibility to fetch it and bind to MVC view (MVC views aren’t aware of model’s existence).
  • setListener(): register a listener with this MVC view that should be notified of user’s actions.

Please note, that implementations of MVC views don’t depend on Activity or Fragment – they can be used with either of them, or even be instantiated inside other MVC views. This takes the idea of reuse of UI elements to the next level and makes any subsequent UI changes way more efficient and less error-prone. Therefore, the next time UX/UI designers asks you to change the appearance of the application, all you need to do is provide different implementations of your MVC views.

But the real advantage of this approach will be felt by those developers, who unit-test their code (I will definitely write many posts on this subject) – you can now easily mock the UI of your application.

In terms of “dirtiness metrics” for UI logic defined in Activities in Android are not UI elements post, the above implementation of MVC view scores 0 “dirtiness points” – it has no direct or transitive dependencies on either Activity or Fragment!

Conclusion:

In this post we saw one possible implementation of MVC view – a standalone UI container which is responsible for presenting application’s output to the client and delivering client’s input to a general object which implements listener interface (this object could be either MVC controller, or another MVC view). MVC views are independent of Activities and Fragments, therefore they can be easily reused, updated or completely replaced, without affecting any existing business logic.

In the next (and last) part of this post we’ll take a look at implementation of MVC controllers in Android.

Please leave your comments and questions below, and consider subscribing to our newsletter if you liked the post.

This article has 15 comments

  1. Deadloque Reply

    Hi, and thank you for this great tutorial, I’m learning a lot, but I’m still a little confused. I don’t understand why you’re saying “MVC views aren’t aware of model’s existence” but the view clearly has a direct dependency on the SmsMessage class, which is part of the model, no ?

    • Vasiliy Reply

      Hi. I appreciate your feedback and question. When I say “model”, I refer to the entity that stores the state of the application (whether this state persistent or not). Objects of SmsMessage class do not store application’s data – they only mirror specific parts of that data in order to be able to pass these parts across application’s layers. Some people call them “Value Objects (VA)”, others call them “Plain Old Java Objects (POJOs)”. There are people (and frameworks) who refer to these objects as “Models”, “View-Models”, “Transactional Models”, etc. As I mentioned in part 1 of this post, MVC is very ambiguous abbreviation which has no widely accepted definition, therefore you can call SmsMessage “Model”, but then substitute each occurrence of “model” with “application’s datastore” (or whatever term you’re comfortable with) when reading these posts.

      • Alexey Reply

        As I understand, view should get only simple data on the input (setMessageText(String); setDate(String)), because controller should decide how to show data. May be, it will be needed to display message’s date in format depend business logic.

        • Vasiliy Reply

          Hello Alexey. Views indeed should get only simple data and POJOs as inputs, and it is view’s responsibility to “decide” HOW to show that data to the user. The responsibility to “decide” WHAT data should be shown belongs to controller.

          • Alexey

            Vasiliy, it’s nice to see the quick response. From where does the view get data to decide how to display it? Imagine the situation: in a group dialog you should display the date in one format and in a single dialog the date should be displayed in other format. How will the view know about the dialog type? For my opinion, this is responsibility of controller.
            But, in most things, your approach is very proper for my opinion. When I started to see into MVC, this thought was the first that visited me. But I am very tired to dispute with my colleagues that activity and fragment should be as a controller or a presenter. They do not want to hear anything. I will write the response after reading the third part. Thank you.

  2. Sushil Kadu Reply

    I understood every single line you wrote. Thank you for such lovely blog.

  3. Pablo Reply

    Thanks for the great lesson. While trying to apply this architecture, I ran into a problem: say I decide to swap the SMS Details view for another one and my boss insists the “mark as read” button must be a button (uncollapsed menu item) in the appbar. How do I do this without messing with the Controller/Presenter?

    • Vasiliy Reply

      Hello and thank you for your question. If such a need arises, you could add CoordinatorLayout and AppBarLayout into mvc_view_sms_details.xml layout file in a usual manner. Then, in the same layout file, add the button to the AppBar and remove the currently used button. Then, in SmsDetailsViewMvcImpl, remove the reference to the current button and register the same listener with the button that is now located in the AppBar. You are done now, and neither the interface of the MVC view, nor the respective controller were affected.

  4. Charles Reply

    Is the ‘Mark as read’ button really doing so? doesn’t seem to actually change the state of the message…

    • Vasiliy Reply

      Hello Charles,
      On the newer versions of Android (starting with Lollipop if I’m not mistaken) the ability to edit SMS messages was reserved to a “default” SMS application. While there is a way to allow the user to change default apps programmatically, I thought that it would be not friendly to make the readers go through such a trouble. Therefore, this functionality only works on older version of Android OS.
      By the way, I will be open-sourcing a real application that uses the approach described in this series of articles and will probably update the examples to use its code.

    • Vasiliy Reply

      Hi. This method was renamed to bindSmsMessage(SmsMessage). I edited the post. Thanks for catching this!

Leave a Comment

Your email address will not be published. Required fields are marked *