Robopupu.Feature API

Applications can be thought to consist of a number of features. Each feature may have zero or more application screens. If MVP architectural design pattern is applied in the architecture of the application, the presentation logic for each screen is implemented as a pair of attached view and presenter. Robopupu uses this concept of features to break down the implementation of the application into a number of feature components. The concept is illustrated in the following diagram:

feature_architecture_overview

Beside presenters and views, an implementation of a feature may require use of other types of components such as models, presentation models, or business logic components.

Each feature should be designed to be as independent and decoupled from other features as possible, because that will promote the following benefits:

  • Features can be implemented and changed independently from each other. This would support, for instance, Feature Driven Development  (FDD) development process.
  • Features can support application product variation (e.g. using Gradle’s product flavors) and configuration at two levels:
    1. Application variants may have different set of features. If features can be developed and managed in an application as kind of plugin components, it will be easier to add, replace, and remove them to produce the needed application variants.
    2. Implementations of basically same feature may differ between two application variants. A feature component itself acts as a configuration object by defining which screens and components are used to implement the feature, and by defining the UI flow and navigation logic for the feature.
  • Features introduce additional level of organisation of code base and encapsulation of overall application logic. Features components specialise on capsulating navigation logic  and  UI flow logic. This aids at understanding and navigating the code base.
  • A feature defines a natural lifecycle scope for a group of inter-related dependencies for dependency injection. In Robopupu architecting, this property of feature components is utilised by implementing each feature component to declare, own,  and manage an implementation of DependencyScope class as described later on.

Key API Classes

The key classes implementing the Feature API are depicted in the following class diagram:

feature_key_classes

  • Feature: An interface for feature components.
  • AbstractFeature: An abstract base class for implementing the Feature interface. Class AbstractFeature is extended from AbstractPluginStateComponent so a concreate implementation of AbstractFeature can be used as a plugin component.
  • FeatureManager: An interface for manager object that is used for creating and starting, and managing Feature instances.
  • AbstractFeatureManager: An abstract base class that provide the core framework functionalities  for implementing the FeatureManager interface. To provide a sample implementation, the Robopupu sample application has a concrete implementation of AbstractFeatureManager in form of plugin component PluginFeatureManager.

Implementing a Feature

Let’s use the simple About feature from the Robopupu sample application as an example about how to implement a feature. We first define the interface of the feature – which is pretty simple in case of About feature:


 

package com.robopupu.feature.about;
import com.robopupu.api.feature.Feature;
import com.robopupu.api.plugin.PlugInterface;

@PlugInterface
public interface AboutFeature extends Feature {
}

 

An important thing to note is that the interface is declared to be a plugin interface by annotating it with the @PlugInterface annotation. This means that we are implementing the About feature as a plugin component that communicates with its Presenter and View implementations and with other components via a plugin bus.

Next we implement the AboutFeature interface by writing a class AboutFeatureImpl. See the walk trough comments written in blue:


package com.robopupu.feature.about;
...
@Plugin // We annotate the implementation to be a plugin component
public class AboutFeatureImpl extends AbstractFeature implements AboutFeature, 
    AboutPresenterListener {
    // We need to use PlatformManager services and that dependency is injected
    // here using the following @Plug annotated field (in similar way as 
    // Dagger 2 uses the @Inject annotation for fields).
    @Plug PlatformManager mPlatformManager;

    // Here we use the Dependency API to define that this class provides 
    // a dependency of type AboutFeature and the DependencyScope for providing
    // that dependency is RobopupuAppScope which is an application level
    // DependencyScope. This means that dependency AboutFeature can be provide
    // everywhere in application
    @Scope(RobopupuAppScope.class) // 
    @Provides(AboutFeature.class)
    public AboutFeatureImpl() {
        // Here we define that this implementation of AbstractFeature owns
        // DependencyScope implementation AboutFeatureScope
        super(AboutFeatureScope.class);
    }

     @Override
     protected void onStart() {
         // AbstractPresenter#showView(Class, boolean) is a convenience method to          // create and show screens. Here we show the About Screen that is 
         // implemented using AboutPresenter and AboutView. 
         // A plugin component that implements the interface AboutPresenter is 
         // also plugged to PluginBus
         showView(AboutPresenter.class, false);
     }

     @Override
     public void onShowLicenseInfo() {
         final Params params = new Params(
             LicensesInfoPresenter.KEY_PARAM_LICENSE_URL, 
             mPlatformManager.getString(R.string.robopupu_license_file));
         // Here we show Lisence Info Screen
         showView(LicensesInfoPresenter.class, true, params); 
     }

     @Override
     public void onShowOssLicensesInfo() {
         final Params params = new Params(
             LicensesInfoPresenter.KEY_PARAM_LICENSE_URL, 
             mPlatformManager.getString(R.string.oss_licenses_file));
         // Here we show also License Info Screen but with different parameters
         showView(LicensesInfoPresenter.class, true, params);
     }

     @Override
     public void onOpenSourcesWebPage() {
         final String url = mPlatformManager.getString(
            R.string.ft_about_text_sources);
         mPlatformManager.openWebPage(url);
     }

     @Override
     public void onPresenterFinished(final Presenter presenter) {
         // Here is instance of navigation logic implemented in a feature.
         // When a presenter for License Info Screen notifies the feature users
         // that the wants to close the screen, it calls listener method
         // the onPresenterFinished(Presenter). As an action, method goBack()
         // that is defined in AbstractFeature is invoked to feature to go back
         // to previous screen.
         if (presenter instanceof LicensesInfoPresenter) {
             goBack();
         }
     }
}

Implementation of the Presenters and Views that AboutFeatureImpl uses is described in detail in Robopupu.MVP API.

Finally, for implementing we need to define a DependencyScope implementation for AboutFeatureImpl. It contains impressively little amount of boiler plate code (which a lazy developer like me writes using a live template in Android Studio):


package com.robopupu.feature.about;
import com.robopupu.api.dependency.DependencyScope;
import com.robopupu.api.dependency.Scope;

@Scope
public class AboutFeatureScope extends DependencyScope {
}

The reason why developers are required to write so few lines of boiler plate code, is that we utilise the Robopupu Compiler that code generates a behind the scenes implementation for AboutFeatureScope (i.e. class AboutFeatureScope_DependencyProvider) based on the @Provides and @Scope annotations given in the classes that implement plugin interfaces. See Robopupu.Dependency API for more details.

Starting a Feature

Starting the created AboutFeatureImpl is pretty simple by using the method FeatureManager#startFeature(FeatureContainer container, Class featureClass). In sample Robopupu application this takes place in MainPresenterImlp attached to MainActivity:


 

package com.robopupu.feature.main.presenter;
...
@Plugin
public class MainPresenterImpl extends AbstractPresenter<MainView>
        implements MainPresenter {

    @Plug AppManager mAppManager;
    @Plug PluginFeatureManager mFeatureManager;
    @Plug MainView mView;

    @Provides(MainPresenter.class)
    public MainPresenterImpl() {
    }
    ...
    @Override
    public boolean onNavigationItemSelected(final int itemId) {
        boolean selectionHandled = true;
        final FeatureContainer container = mView.getMainFeatureContainer();

        if (itemId == R.id.navigation_about) {
            mFeatureManager.startFeature(container, AboutFeature.class);
        } else if (itemId == R.id.navigation_feedback) {
            mFeatureManager.startFeature(container, FeedbackFeature.class);
        } else if (itemId == R.id.navigation_fsm_demo) {
            mFeatureManager.startFeature(container, FsmDemoFeature.class);
        } else if (itemId == R.id.navigation_exit) {
            mAppManager.exitApplication();
        } else {
            selectionHandled = false;
        }
        return selectionHandled;
    }
}

 

The classes involved in showing a feature are depicted in the following class diagram:

feature_container

 

Descriptions of the depicted classes:

  • FeatureFragment: An abstract base class for implementing fragments for features. Robopupu.Feature API provide also the FeatureDialogFragment base class.
  • FeatureTransitionManager: An interface for an object that can be used to show the FeatureFragments of a Feature. Since FeatureFragments are Fragments, an implementation of  FeatureTransitionManager interface needs to use Android’s FeatureManager to implement the fragment transactions.
  • FeatureContainer: An interface that is used to represent an container for the root android.view.View of a Fragment. An Activity may provide one or more FeatureContainers. That is why the framework includes the interface FeatureContainerProvider. The MainActivity class in Robopupu sample application implements both the FeatureContainer and FeatureContainerProvider interfaces via its superclass PluginActivity. If Android Support Library is used  for implementing applications UI, then PluginCompatActivity class has to be used instead PluginActivity.
  • FeatureContainerProvider: The interface which FeatureManager utilises to obtain the list of FeatureContainers from an Activity that is currently in foreground.

 

Package Organisation

The recommend way is to organise classes of each implemented feature, into a dedicated package as depicted in the following image:

package_organisation

 

Robopupu sample application has five features: About, Feedback, FSM Demo, Chuck Norris Jokes, and Main. Each feature has its own dedicated package under package com.robopupu.feature.  The Presenter and View implementation classes are placed into packages <feature>.presenter and  <feature>.view respectively. Business logic components, such as interactor components, into package <feature>.component. Models and model objects are places to package <feature>.model.  This kind of convention in organising the classes into packages according to their type and by reflecting the architecture helps in navigating the entire code base.