MVP and MVC

This is the third (and last) 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 2).

In the previous post of this series we saw how to extract UI logic into standalone classes and “hide” implementation details behind an interface. I mentioned that this abstraction of UI logic is, in my opinion, the most important aspect of MVC.

We also mentioned that there is no particular difficulty with implementation of MVC model in Android.

Therefore, in order to conclude our thorough review of MVC, the only component left for discussion is MVC controller.

MVC controller:

Each application has its own functionality which is achieved by careful definition of set of rules by developers. This set includes (but is not limited to) rules that define how application:

  • consumes inputs and produces outputs (data, events and interactions)
  • manages its internal state
  • handles device state changes (connectivity, location, orientation, etc.)
  • interacts with OS and other applications (taking pictures, setting alarms, etc.)

However, this set does not include the functionalities of MVC views and MVC models: both the way you present the output to the client and the way you store application’s internal state are implementation details. These details should not affect the above rules in any way, and, ideally, you should be able to switch to a different implementation at any time without modifying any of the rules.

The above set of rules, when captured in code, constitutes application’s business logic. In MVC, controllers are the components that encapsulate application’s business logic.

MVC controllers implementation:

As we discussed in part 1 of this series, Activities and Fragments are MVC controllers. If you still feel uncomfortable with this statement, you can read the article explaining why Activities in Android are not UI elements.

The general idea is that each MVC controller instantiates (or gets through injection) one or several MVC views and “offloads” UI related tasks to them, while executing application’s business login and “talking” to MVC model in order to update or query application’s state.

As an example, let’s review the implementation of SmsDetailsFragment:

/**
 * This fragment is used to show the details of a SMS message and mark it as read
 */
public class SmsDetailsFragment extends BaseFragment implements
        SmsDetailsViewMvc.ShowDetailsViewMvcListener,
        SmsMessagesManager.SmsMessagesManagerListener {

    /**
     * This constant should be used as a key in a Bundle passed to this fragment as an argument
     * at creation time. This key should correspond to the ID of the particular SMS message
     * which details will be shown in this fragment
     */
    public static final String ARG_SMS_MESSAGE_ID = "arg_sms_message_id";

    private SmsDetailsViewMvc mViewMVC;

    private SmsMessagesManager mSmsMessagesManager;

    private long mSmsMessageId;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {

        // in general, better use dependency injection library (e.g. Dagger 2) for members' init
        mSmsMessagesManager = new SmsMessagesManager(
                getActivity().getContentResolver(),
                getMainThreadPoster(),
                getBackgroundThreadPoster());

        // Get the argument of this fragment and look for the ID of the SMS message which should
        // be shown
        Bundle args = getArguments();
        if (args.containsKey(ARG_SMS_MESSAGE_ID)) {
            mSmsMessageId = args.getLong(ARG_SMS_MESSAGE_ID);
        } else {
            throw new IllegalStateException("SmsDetailsFragment must be started with SMS message ID argument");
        }

        // Instantiate MVC view associated with this fragment
        mViewMVC = new SmsDetailsViewMvcImpl(inflater, container);
        mViewMVC.setListener(this);

        /*
        Starting with API 19 (KitKat), only applications designated as default SMS applications
        can alter SMS attributes (though they still can read SMSs), therefore on post KitKat
        versions "mark as read" button is only relevant if this app is the default SMS app.
         */
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            String defaultSmsPackage = Telephony.Sms.getDefaultSmsPackage(getActivity());
            if (!getActivity().getPackageName().equals(defaultSmsPackage)) {
                mViewMVC.markAsReadNotSupported();
            }
        }

        // Return the root view of the associated MVC view
        return mViewMVC.getRootView();
    }

    @Override
    public void onStart() {
        super.onStart();
        mSmsMessagesManager.registerListener(this);
        mSmsMessagesManager.fetchSmsMessageById(mSmsMessageId);
    }

    @Override
    public void onStop() {
        super.onStop();
        mSmsMessagesManager.unregisterListener(this);
    }

    @Override
    public void onMarkAsReadClick() {
        mSmsMessagesManager.markMessageAsRead(mSmsMessageId);
    }

    @Override
    public void onSmsMessagesFetched(List<SmsMessage> smsMessages) {
        for (SmsMessage smsMessage : smsMessages) {
            if (smsMessage.getId() == mSmsMessageId) {
                mViewMVC.bindSmsMessage(smsMessage);
                return;
            }
        }
        Toast.makeText(getActivity(), "Couldn't fetch the SMS message of interest!", Toast.LENGTH_LONG).show();
    }

}

The main functionality of this controller is the following:

  1. Fetch data of a specific SMS message from the model
  2. Bind the fetched data to MVC view
  3. Mark the SMS message as “read” in model upon receiving notification about user interaction from MVC view

However, the important part is THE LOGIC THE CONTROLLER DOES NOT CONTAIN:

  1. MVC controller does not contain UI implementation details (i.e. no dependency on Android View class, no references to R.id.* identifiers, etc.) – we could introduce another implementation of SmsDetailsViewMvc, having a completely different UI, and switch to the new implementation by changing one line of code inside MVC controller (and it would be no changes at all if we’d used dependency injection framework or Factory design pattern).
  2. MVC controller does not contain implementation details of data storage mechanism – controller depends only on the contract of ContentProvider which is being queried.

Another important point to note is the usage of SmsMessagesManager class. This class encapsulates business logic related to SMS messages. SmsMessagesManager is also part of a “generalized” controller. Therefore, we shouldn’t necessarily put all the business logic into Activities and Fragments – controllers can be composite objects, and, in general, must adhere to SOLID principles. [Some people would claim that such “managers” are part of a model rather than part of a controller. I wouldn’t agree with them, but, as long as the code is clean, wouldn’t argue either.]

In terms of “dirtiness metrics” defined in Activities in Android are not UI elements post, the above implementation of controller scores 1 “dirtiness point” (due to dependency on SmsDetailsViewMvcImpl). We could get it down to 0 score by using dependency injection techniques (I didn’t want to complicate the code).

Introduction of MVC into codebase:

If you want to try MVC, you should understand that there will be difference between new projects written from scratch, and projects that already have some legacy code.

For new projects just start with MVC and stick to the principles. It will take some time to get accustomed to it, but in a matter of days you’ll learn the technique, and once you’ve got it – it will feel the most natural thing to do.

For existing projects I would recommend NOT doing “the big refactoring”. Don’t schedule weeks for long refactoring activities. Instead, each time you need to make a change to one of your Activities or Fragments, just convert them into MVC controllers one at a time (by extracting logic into MVC views and models). This will allow the project to keep the pace while being gradually refactored to a clean, decoupled and maintainable code.

Conclusion:

In this series of posts we discussed the general aspects of MVC and MVP architectural patterns, and provided an example of one possible implementation of MVP pattern (which we agreed to call MVC) for Android development.

Sometimes, due to a coupling introduced at Android OS level, it is not trivial to implement an ideal MVC. While the examples used in tutorial application are simple, complex UIs having rich functionality will also benefit from transition to MVC (in fact, the more complex the system, the bigger benefits you’ll get by MVC’ing it).

I also open-sourced a real application that uses MVC. You can review the source code of this application in order to see how MVC integrates into larger projects. Try to read the source code of several controllers and note how easy it is to understand what they do. Also use the code as a reference implementation for a more complicated UI constructs (lists, navigation drawer, toolbar, etc.).

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

This article has 11 comments

  1. Exerosis Reply

    I know it has been awhile since this was posted. However, I have a few questions if you are still around to answer them 🙂 So! I just started using MVC for the first time, and I think I can say I really like it! But there are a few things I have no idea how to properly implement. Namely how to handle fragments properly, in my app, I have one view that is capable of displaying a grid of images passed from my Controller(the fragment class). I would like to add a navdrawer to the project, that navdrawer should change what images are loaded, but preferably not switch out the fragment. That pushes me to say I need to have a Controller interface that my fragment can implement, that way when you go to create the fragment you just keep an instance and you can call .setImageProvider() or whatever. But this gets messy very quickly! The question is, how should I handle things like nav drawers, that should be app-wide on most devices, and yet need to communicate with their child fragment(s).
    Apologies on such a long post, I hope this makes a little sense 😛

    • Vasiliy Reply

      Hello and thank you for your question.
      So, basically, you want to affect the contents of various Fragments in your app from NavDrawer, right? This can be further generalized to a question of communication between two independent MVC Controllers in the application (e.g. Fragment displayed in Toolbar and Fragment displayed as main content).
      I see at least two possible approaches:
      1) The scheme you described is actually very close to the officially recommended approach for communication between Fragments. However, I think your analysis is correct and this kind of code increases system’s inter-coupling and is not easy to maintain in the long run.
      2) The better approach (IMHO) is to make use of event bus – “master” MVC Controller posts “update” events to event bus when user interactions occur, whereas “slave” MVC Controller registers itself to event bus in onStart() (unregisters in onStop()) in order to receive these events. Please note that I intentionally say “MVC Controller” and not “Fragment” – both the “master” and the “slave” could be Activities or Fragments (or even Services, which are not part of MVC at all). All my projects use Green Robot’s EventBus, and I can’t recommend it highly enough.

  2. Marija Reply

    Thank you so much for this! If it’s not a problem, could you please tell me how you MVCed the navigation drawer? I’m not sure what the right approach is for it.

  3. Damian Reply

    Hi Vasiliy. Thanks to your project I have finally understand the concept of the MVC pattern. But I have a questions regarding the structure of your sample project and structuring the future projects. So. In your project you named one of its root packages the “screens” package what implies that this package contains screens visible to the user. Does in this context placing the “common” package in the “screens” package where it is only a host for other screens is appropriate or not?

    Beside, after opening your sample project I misunderstood the concept of the “common” package. I thought that it contains elements used by packages of other screens and de facto it is true. It contains base elements (BaseFragment and ViewMvc) used in other screen packages. But as I mentioned above it is also a host for other screens. So, should not the “common” package be renamed? Or at least elements (BaseFragment and ViewMvc) should not be moved into other package outside the “common” and “screens” packages?

    • Vasiliy Reply

      Hi Damian, and thanks for your question.
      What you’re asking about is not strictly related to MVC, but to the general organization of project’s source code. When I come to think about it now, this topic definitely deserves a post by its own. The below explanation is just a brief summary of my approach, but I think it will be enough for you because you got most of it by yourself already.
      Top level packages reflect application’s “purpose”. For any application of considerable size, most of top level packages will borrow their names from the “business/problem domain”. In case of MVC tutorial app, which is SMS reader, there is a top-level package named “sms”. This package contains SmsMessage and SmsMessagesManager classes. In addition, there is top-level package named “common”. This package contains classes and sub-packages that can be used by multiple other top-level packages (therefore “common”).
      Now, it could be very convenient to associate screens (which are view-controller pairs with related logic) with the aforementioned top level packages. In practice, however, one screen can depend on multiple top level packages. The tutorial app is very simple, but imagine that we add messages aggregation by contact. Then we would add “contacts” package and “smsall” screen would use both “sms” and “contacts” packages. Therefore, screens can’t be associated with existing top level packages. This is the reason we put them as sub-packages in a dedicated top-level package named “screens”. But different screens can share logic (e.g. base classes). I put this common logic related to screens into “common” sub-package of “screens” package.
      Let’s summarize. Top level packages reflect application’s “purpose”. Ideally, you should be able to understand what business/problem domain the application targets just by reading the names of the top level packages. In addition, there will be “common” and “screens” top level packages. Each application’s screen will be represented by a sub-package in “screens” top-level package. In addition, there will “common” sub-package in “screens” for logic that is shared between multiple screens. I hope this clarifies the structure a bit.
      * I updated the tutorial application in order to reflect the structure described above.

      • Damian Reply

        Thanks for your quick replay and clarification. If you do not mind I would be grateful If you validate or not my understanding. Suppose, that your SMS Reader application provides a settings screen. And it allows to set different options of settings for contact and SMSs. According to your explanation. The “screens” top-level package should contain the “contactssettings” and “smssettings” sub-packages that will represent application’s screens. I know that the both of the settings screens must have their own controllers in their “controller” sub-packages of their packages. Moreover, the “controller” package in the “common” sub-package in the “screens” top-level package should contain a class (e.g SettingsActivity) that provides the same logic and it is also the host for “contactssettings” and “smssettings” screens as the MainActivity class for “smsall” and “smsdetails” screens. But I want the “contactssettings” and the “smssettings” screens logic to be separated because I want them to be managed as a separated android task or just by design. And the top-level package named “settings” (containing class providing interface to write/read application preferences) should be added to application’s project structure that will be used by “contactssettings” and the “smssettings” screens packages. Is my understanding correct?

        • Vasiliy Reply

          Your understanding is correct.
          Few additional points to consider in respect with your specific example:

          1) Decide whether “settings” is a first class functionality of the app. If it is, then additional top-level package is appropriate. If it isn’t, then I’d put “settings” package into “common” top-level package (and, in fact, this is exactly what I did in IDoCare app)

          2) If “contactssettings” and “smssettings” are similar in functionality, then it might be better to put them in a single sub-package inside “screens”. Then, SettingsActivity could reside in this package too. This way you’ll have all settings screens related functionality in a single package. Both approaches are good, though, therefore it is more a matter of preference.

          3) If you want settings screens to run as different tasks, then you’ll need a separate Activity for each of them. This is not exactly analogous to MainActivity and “sms” screens which are implemented using so called “single Activity” approach.

          4) If you have both Activities and Fragments in some sub-package of “screens”, you can split “controllers” package into “activities” and “fragments” packages for readability. This is, again, a matter of personal preference.

  4. Kike Bodi Reply

    Thanks this great tutorial.

    I’m missing something, I don’t see exactly where we have to implement the model as we implement the Controler at the SmsDetailsFragment and the View at ViewMvc, SmsDetailsViewMcv and SmsDetailsViewMcvImpl.

    Is it SmsMessagesManager the model or it’s SmsDetailsViewMcvImpl?

    • Vasiliy Reply

      Hi,
      The state that is being dispalyed is in the database of SMS messages which is not part of this application at all. Therefore, the model in this case is external to the application and is being accessed via ContentProvider.

      Whether SmsMessagesManager is part of a model or a controller is kind of debatable. I prefer to see it as part of the controller because it does not have a state by itself. Some people find it conceptually easier to see it as part of the model. As I said in the article, it doesn’t really matter – as long as this component is there and provides the required level of abstraction and encapsulation, call it whatever you like.

Leave a Comment

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