Composite Views in Android: Composition over Inheritance

Presenting the topic

Few days ago, I run into CompositeAndroid (Android library). If you haven’t heard of it, it favours composition over inheritance in Android views (activity and fragments, right now). Instead of having multiple base classes that add functionality to an Activity or Fragment, you add behaviours (so called plugins in the library) that contains that functionality to your view.

Having base Activities or Fragments with shared logic is quite common in our apps. This happens because we need that logic to be shared between some classes (not all of them!). And that happens over time, not always at the same time (usually when we build new features). Here is when the problem comes. Because it’s just used by some of them, we create this endless and unsustainable hierarchy tree.

Example

Let’s say you have a HomeActivity which extends from a BaseTintedActivity that extends from a BaseSecureActivity that extends from a BaseActivity that extends from… etc … that extends from an AppCompactActivity.

Solution

You would have just an Activity extending from AppCompactActivity (so called CompositeActivity in the library) and your HomeActivity would extend from that one.

Then, the shared functionality will be added to your HomeActivity as “plugins”. You’d add an SecureActivityPlugin, TintedActivityPlugin, etc.

Problems

I do love the concept! I think it’s great, it prevents this inheritance hell that we all have at some point. It makes the code more sustainable and amenable to change.

However, if you decide you’re going to use the library (CompositeAndroid), there are some caveats that you need to be aware of:

  • It’s still in an early alpha stage.
  • Although nothing breaks when you extend its CompositeActivity, you might not work if you use non-so-common lifecycle methods.
  • It’s attached to the Android SupportLibrary. This means that you cannot update your SupportLibrary version if CompositeAndroid does not do it.

My main worry is the last point. Having my app so attached to another library in this really deep level doesn’t make me too comfortable. And if I want to change it, the refactoring involved is going to be really time consuming.

Moreover, I don’t need to access to all the possible methods available in the View. Just want to make it easy to understand and only access to what I need.

Potential solution

Considering the problems I mentioned above, I’d like to work on a solution that is simple to maintain and has the same benefits as CompositeAndroid.

Again, I love the concept! I want to avoid this inheritance hell and promote composition. Let’s see how we could do it with the previous example.

Let’s start with something easy first. If we go back to the example, our HomeActivity extends from BaseSecureActivity in our inheritance world. BaseSecureActivity doesn’t allow users to take screenshots. It’d be something like:

public abstract class BaseSecureActivity extends BaseActivity {

@Override
protected void onResume() {

super.onResume(); getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
}
}

How we could convert this to a composition approach?

Ideally, to not interfere with the lifecycle methods, we’d like to add the different behaviours in the constructor.

Our new HomeActivity extends from CompositeAppCompatActivity (we’ll see this class later on). In the constructor, we just call the method addBehaviour with a new instance of the Behaviour we want it to have. The behaviour accesses the activity Window (method getWindow()) to block taking screenshots. Because of this, the behaviour needs to know about the Activity which is passed to the Behaviour constructor.

public class HomeActivity extends CompositeAppCompatActivity {

public HomeActivity() {

addBehaviour(new SecureActivityBehaviour(this));
}
}

Easy, isn’t it? What about the Behaviour then? How’s that implemented? Basically, a Behaviour responds to lifecycle events. To notify those, we can create an interface with a method that receives an ActivityState as parameter. An ActivityState can be just a simple enum.

public interface ActivityBehaviour {
    void onActivityEvent(ActivityState activityState);
}

If we just had the RESUME and DESTROY event, the ActivityState would look like:

public enum ActivityState {

RESUME,
DESTROY
}

The SecurityActivityBehaviour implements that interface and acts when the RESUME event is sent. We keep the instance of the activity as a WeakReference to avoid memory leaks.

public class SecureActivityBehaviour implements ActivityBehaviour {

private WeakReference<Activity> activity;

public SecureActivityBehaviour(Activity activity) {

this.activity = new WeakReference<>(activity);
}

@Override
public void onActivityEvent(ActivityState activityState) {

switch (activityState) {
case RESUME:
Activity realActivity = activity.get();
if (realActivity != null) {
realActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
}
break;
}
}
}

As you can see, the SecureActivityBehaviour has the same functionality as the BaseSecureActivity class that we saw before.

We extended the HomeActivity from CompositeAppCompactActivity… show me the code! Basically… we create a List of behaviours and depending on the lifecycle method, we notify the behaviours with the right event.

public class CompositeAppCompatActivity extends AppCompatActivity {

private final ArrayList<ActivityBehaviour> behaviours = new ArrayList<>();

@Override
@CallSuper
protected void onResume() {

super.onResume();
for (ActivityBehaviour activityBehaviour : behaviours) {
activityBehaviour.onActivityEvent(ActivityState.RESUME);
}
}

@Override
@CallSuper
protected void onDestroy() {

for (ActivityBehaviour activityBehaviour : behaviours) {
activityBehaviour.onActivityEvent(ActivityState.DESTROY);
}

removeBehaviors();
super.onDestroy();
}

protected void addBehaviour(ActivityBehaviour activityBehaviour) {

behaviours.add(activityBehaviour);
}

protected void removeBehaviours() {

behaviours.clear();
}
}

That’d be it!

Extending the example: More lifecycle events

public enum ActivityState {

CREATE,
START,
RESUME,
PAUSE,
STOP,
DESTROY
}

Extending the example: More complex behaviours

Your app might have a different architecture from what we’ve seen above. You might use dependency injection with Dagger and your behaviour requires some objects injected into it (Managers, Singletons, …). No worries, we could achieve it by adding the behaviour after the Dagger component is been injected. With this, we might lose some the onCreate event although it could be easily fixed.

@Inject ComplexActivityBehaviour complexBehaviour;

@Override
protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
    // Inject component
    addBehaviour(complexBehaviour);
}

Extending the example: Other ideas

  • We might want to have more flexibility. If we want a behaviour to act before or after the onCreate on the Activity is called we could do that manually. We just had to create two more lifecycle events (CREATE_BEFORE, CREATE_AFTER) and modify the CompositeAppCompatActivity to achieve this new functionality.
  • Does it mean that all the behaviours are going to receive the same events? Even if they don’t process it? Well… That’s what happens right now but it can be easily solved. Create different types of “ActivityState” enums and call them when appropriate. Examples: ActivityState, ActivityUserInteraction (include Activity.onUserInteraction() here), etc.

Thanks for reading,

Manuel Vicente Vivo

Edit: 27th February, 2017

Clarifications about CompositeAndroid from Pascal Welsch (author of it):

  • The project stage isn’t “early alpha” anymore. I just haven’t updated the README since the first draft, sorry.
  • All Activity/Fragment methods are supported. There were some edge cases but everything is solved.
  • You can update the SupportLibrary without updating CompositeAndroid. Most updates are just cosmetic to update the documentation. Yes, there was a breaking change in 24.2.1 but it hasn’t happened again in the recent 8 updates.