So, you want to do MVC...

Overview

A full explanation of the MVC framework can be found here. This document is intended to go over the logistics of the most basic implementation of the framework in Chrome’s codebase.

For this example, we’ll be implementing a simple progress bar; a rectangle that changes length based on the loading state of the underlying webpage.

Additional Resources

Simple MVC lists Testing MVC primer doc

File Structure

The file structure of our component will be the following:

SimpleProgressCoordinator

The class responsible for setting up the component. This should be the only public class in the component's package and is the only class with direct access to the mediator.

public class SimpleProgressCoordinator {

   private SimpleProgressMediator mMediator;

   public SimpleProgressCoordinator (Tab tabProgressBarIsFor) {

       PropertyModel model = new PropertyModel.Builder(SimpleProgressProperties.ALL_KEYS)
               .with(SimpleProgressProperties.PROGRESS_FRACTION, 0f)
               .with(SimpleProgressProperties.FOREGROUND_COLOR, Color.RED)
               .build();

       // This view can come from multiple places, in this case we find it in the existing
       // view hierarchy.
       View view = tabProgressBarIsFor.getActivity().findViewById(
               R.id.my_simple_progress_bar);

       PropertyModelChangeProcessor.create(model, view, SimpleProgressViewBinder::bind);

       mMediator = new SimpleProgressMediator(model, tabProgressBarIsFor);
   }

   public void destroy() {
       mMediator.destroy();
   }
}

SimpleProgressMediator

The class that handles all of the signals coming from the outside world. External classes should never interact with this class directly.

class SimpleProgressMediator extends EmptyTabObserver {

   private PropertyModel mModel;

   private Tab mObservedTab;

   public SimpleProgressMediator(PropertyModel model, Tab tabProgressBarIsFor) {
       mModel = model;
       mObservedTab = tabProgressBarIsFor;
       mObservedTab.addObserver(this);
   }

   @Override
   public void onLoadProgressChanged(Tab tab, int progress) {
       mModel.set(SimpleProgressProperties.PROGRESS_FRACTION, progress / 100f);
   }

   @Override
   public void onDidChangeThemeColor(Tab tab, int color) {
       mModel.set(SimpleProgressProperties.FOREGROUND_COLOR, color);
   }

   void destroy() {
       // Be sure to clean up anything that needs to be (in this case, detach the tab
       // observer).
       mObservedTab.removeObserver(this);
   }
}

SimpleProgressViewBinder

The class responsible for applying a model to a specific view. In general there is a 1:1 relationship between a type of view and its binder. Multiple binders can know how to take a single type of model and apply it to a view. The binder's method should be stateless; this is implied by the ‘static’ identifier.

class SimpleProgressViewBinder {

    public static void bind(PropertyModel model, View view, PropertyKey propertyKey) {
        if (SimpleProgressProperties.PROGRESS_FRACTION == propertyKey) {

            // Apply width modification to 'view' here.

        } else if (SimpleProgressProperties.FOREGROUND_COLOR == propertyKey) {

            // Apply color modification to 'view' here.

        }
    }
}

SimpleProgressProperties

These are properties associated with the view the model will be applied to.

class SimpleProgressProperties {

    public static final WritableFloatPropertyKey PROGRESS_FRACTION =
            new WritableFloatPropertyKey();

    public static final WritableIntPropertyKey FOREGROUND_COLOR =
            new WritableIntPropertyKey();

    public static final PropertyKey[] ALL_KEYS = {PROGRESS_FRACTION, FOREGROUND_COLOR};
}