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 5 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.

Leave a Comment

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