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

What function MVC controllers fulfill:

We have already seen that MVC views take on responsibility of handling input/output of data and interactions with the client (whether human or not). We also mentioned that MVC models store application’s state (whether persistent or not). What is left for MVC controllers to do?

Each application has its own functionality which is achieved by careful definition of set of rules by a programmer. 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.)
  • many more

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.

How to implement MVC controllers:

As discussed in Activities in Android are not UI elements post, due to very tight coupling to various Android frameworks, Activities and Fragments are the best candidates for MVC controllers.

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, which encapsulates business logic related to SMS messages. This class is part of a “generalized” controller. Therefore, when I say that controllers contain application’s business logic, I don’t imply that we should put all business logic into Activities and Fragments alone – 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” for business logic 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, but I didn’t want to complicate the tutorial.

I want to give it a try! How to start:

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 clean, decoupled and maintainable architecture.

Conclusion:

In this series of posts I presented you a 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) in 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 template/tutorial application in order to demonstrate the principles are very simple, complex UIs having rich functionality will also benefit from transition to MVC (in fact, the more complex the system, the bigger ROI you’ll get by MVC’ing it). Among UI’s that I have already MVC’ed myself or guided others to do so were:

  • Lists
  • Tabbed layouts
  • NavigationDrawer
  • CoordinatorLayout with AppBarLayout and CollapsingToolbarLayout
  • Custom layouts with animations

The bottom line is that you can get the benefits of MVC in applications of any level of complexity, and the more complex your project, the more you will gain by MVC’ing it. Give it a try!

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

This article has 4 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.

    • Vasiliy Reply

      Marija, thank you for warm words.
      Since you can have a Fragment as nav drawer’s contents, it is basically the same as for any other Fragment. I also suggest that you create NavDrawerManager class – this class will be used by other Activities/Fragment in order to change title, open/close nav drawer, etc.
      I will be open sourcing my application soon – you could get some ideas from there.

Leave a Comment

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