MVC Architectural Pattern in Android – Part 2

In the first post in this series I provided a high-level overview of Model View Controller (MVC) architectural patterns in context of Android development. The goal of this second part is to explain how to extract UI logic in your application into a standalone view component.

Difference Between MVC View and Android View

Android framework already contains a component called View. It’s 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 interface” (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:

/**
 * MVC view is a "dumb" component used for presenting information to the user and capturing user's input.
 */
public interface ViewMvc {

    /**
     * @return the root Android View which is used internally by this MVC View.
     */
    View getRootView();

}

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. However, this interface does not have any means for differentiating between different MVC views. Therefore, each concrete MVC view will need to be defined as sub-interface of ViewMvc, and controllers will depend on these sub-interfaces.

The above dependency does not violate MVC paradigm because controllers are allowed to know about MVC views. However, this dependency should be constrained to just the high-level interface. Therefore, keep in mind that you should avoid coupling controllers to internal implemenation details of MVC views.

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

public interface SmsDetailsViewMvc extends ViewMvc {

    interface Listener {
        void onMarkAsReadClicked();
        void onNavigateUpClicked();
    }

    void hideMarkAsReadButton();

    void bindSmsMessage(SmsMessage smsMessage);

    void registerListener(Listener listener);
    void unregisterListener(Listener listener);

}

Please note that just by looking at this interface you can immediately know what this MVC view does. This is the power of a good abstraction: it allows us to understand the interactions supported by this MVC view, while ignoring the implementation details.

In some sense, these abstractions are the most important feature of MVC.

The actual class that implements SmsDetailsViewMvc interface is SmsDetailsViewMvcImpl:

public class SmsDetailsViewMvcImpl extends BaseViewMvc<SmsDetailsViewMvc.Listener> 
        implements SmsDetailsViewMvc {

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

    private boolean mMarkAsReadSupported = true;

    public SmsDetailsViewMvcImpl(LayoutInflater inflater, ViewGroup container) {
        setRootView(inflater.inflate(R.layout.layout_sms_details, container, false));

        mToolbar = findViewById(R.id.toolbar);
        mTxtAddress = findViewById(R.id.txt_sms_address);
        mTxtDate = findViewById(R.id.txt_sms_date);
        mTxtBody = findViewById(R.id.txt_sms_body);
        mBtnMarkAsRead = findViewById(R.id.btn_mark_as_read);

        mToolbar.setNavigateUpListener(() -> {
            for (Listener listener : getListeners()) {
                listener.onNavigateUpClicked();
            }
        });

        mBtnMarkAsRead.setOnClickListener(view -> {
            for (Listener listener : getListeners()) {
                listener.onMarkAsReadClicked();
            }
        });
    }

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


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

        int backgroundColor =
                smsMessage.isUnread() ? android.R.color.holo_green_light : android.R.color.white;

        getRootView().setBackgroundColor(getColor(backgroundColor));

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

    }
}

As you can see, this MVC is relatively simple, but it won’t always be the case. Complex MVC views can grow to hundreds of lines of code. When this happens, having an interface for MVC view is especially beneficial because it allows you to understand the contract of the view without reading (potentially messy) implementing code.

Let’s understand what’s going on the above implementation of MVC view:

  • Constructor: the main aspect of MVC views’s construction is initialization of the underlying Android “root” View from layout XML file. Once root Android View is initialized, you will proceed with initialization of other Android Views contained in the inflated hierarchy, including registration of listeners for user interactions.
  • bindSmsMessage(SmsMessage): this MVC view renders SMS’s data on the screen. It’s controller’s responsibility to fetch the data and bind it to MVC view.
  • hideMarkAsReadButton(): self explanatory.

Please note that implementations of MVC views don’t depend on either Activity or Fragment. MVC views can be used with either of them, or even be nested inside other MVC views. This takes the idea of reuse of UI elements to the next level and makes any subsequent UI changes simpler and less error-prone.

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 you learned how to implement MVC view. This is a standalone UI container which is responsible for rendering application’s output on the screen and capturing client’s interactions with the UI. 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 post in this series I’ll show you how to implement MVC controllers and interconnect everything together into a fully-functional MVC.

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

Check out my premium

Android Development Courses

27 comments on "MVC Architectural Pattern in Android – Part 2"

  1. 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 ?

    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.

      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.

        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.

          Reply
          • 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. 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?

    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.

      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.

      Reply
    • The difference between interface and abstract class is that abstract class not only defines a contract, but also provides functionality. If you are asking whether ViewMVC could be abstract class instead of interface – yes.

      In fact, if you take a look at the screens.common.mvcviews package in my template application, you will notice that there is an abstract class which provides defines functionality common to all MVC views. Given that I have this abstract class, there is not very much value in having the interface: it just makes the contract easier to read.

      Therefore, your instincts are good – I could’ve done this with just one abstract class. I guess it is just my strive to absolute perfection that made me wrap everything in interfaces…

      Reply
  3. Hey, a little question. How would you approach implementing view with actionbar and drawer. I mean there are some steps while initializing them in which you have to use activity methods (like activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true)).
    I decided to use constructor(LayoutInflater, AppCompatActivity), but as you could guess it creates dependency on activity, so I was wondering if there is better solution. Thanks in advance.

    Reply
    • Hi Alexander,
      As of today, I wouldn’t recommend using any of ActionBar’s APIs. It is way easier to use Toolbar directly, and gives you more control over what’s happening.
      If you do find yourself in position that MVC view needs to use some Activity API, you have two options:
      1) Pass Activity into the view (as you did)
      2) Wrap Activity in a class that exposes the required functionality and pass that class into the view
      I’d say that option #2 is way better and cleaner. This way, view doesn’t depend on Activity, and it greatly reduces the chance of polluting the view with non-UI logic.
      For example, in your case, you could create a class named ActionBarController (or any other name that you like). This class will expose method initActionBar() that will internally call Activity’s APIs. Relevant views will get this controller and call to initActionBar() when needed.
      My open-sourced application IDoCare contains both Toolbar and NavDrawer. You can have a look at its code and get some initial ideas for your own implementation: https://github.com/techyourchance/idocare-android

      Reply
    • Hi Gabor,
      It will be set by Fragment’s onCreateView() if save & restore will happen.
      However, I see your point – this approach is ugly.
      In fact, this entire codebase is a mess by my today’s standards. It evolved over several years together with my understanding of Android and I’m not satisfied with its state at all.
      For a very long time now I want to rewrite the tutorial application from scratch, but never find time to do so.
      One day, hopefully, I’ll get to it…

      Reply
  4. Hello Vasili! This post is just lovely and is the first one that I’ve understood and implement as an architectural pattern. I have been advised to check out also clean architecture of uncle Bob and since I am a big fan of his sometime in the future I will.

    My query: I have noticed that I have never used ViewMvc.getViewState(). It always returns null.

    I find it logical to save the android view’s state inside the MVC View but what if when a configuration change happens and the activity/fragment is recreated. Also it’s dependencies are recreated and thus the MVC View that this controller is depended upon.

    So how the MVC View is going to retain its state apart from persisting it in the hard disk (using SharedPreferences for example) ?

    I hope I was clear.

    Reply
    • Hi Themelis,
      Thanks for your question.

      1)
      I don’t have getViewState() method in ViewMvc interface anymore. I found this approach to be clumsy and unnecessary.
      You can find a more up-to-date version of this MVC pattern implemented in the tutorial application for my Dependency Injection course. It’s also a much more “real” implementation than the tutorial app for this post. It might also be of interest to you if you’d like to explore what Uncle Bob called “clean architecture”.
      I will update this post and the tutorial application according to the insights from the last 1-2 years sometime this month.

      2)
      It seems like you’re asking about whether the state of Android Views inside ViewMvc implementations will be restored on e.g. config change. The answer is yes.
      Even if you don’t use this MVC approach, all the Android Views associated with e.g. Activity will be destroyed and re-created on config change. Android can still restore their state after the new View hierarchy is re-created and bound to Activity (as long as View ID’s remain the same). So, as long as you instantiate the same ViewMvc after config change – Android will restore the state.

      Hope this helps.

      Reply
  5. Hi Vasili, hope you’re well!

    I believe I know the answer but just to hear it from you to be sure 100%.

    Inside your text we can read… “the above implementation of MVC view scores 0 “dirtiness points” – it has no direct or transitive dependencies on either Activity or Fragment!”.
    What if this MVC View depended on Context class, would it score 0 dirtiness points?

    Thanks again!

    Reply
    • Hello Themelis,

      Context is a tricky case. I discussed it in the article where I defined the “dirtiness metrics”, but will repeat here for your convenience:

      Note that this test is also “transitive” – there should be no “dependency chain” from classes that encapsulate UI logic to Activities. Unfortunately, I can’t define dependency on Context as “dirtiness point” – you’ll need to provide a Context to UI encapsulating classes because there is no way to inflate a View without a reference to some Context (Context are God Objects, remember?).

      So, the answer is that it’s OK to have a reference to Context in MVC view (in fact, you can’t avoid that), but you need to be careful with it because Context is God Object and breaks encapsulation.

      Reply

Leave a Comment