midnite
midnite

Reputation: 5286

Adding Functionality to both Android's Activity (parent class) and FragmentActivity (child class) in best coding style (minimal duplication)

In Android Library, FragmentActivity extends Activity. I would like to add a few methods, and override some methods, of the original Activity.

import android.app.Activity

public class Activiti extends Activity {
    public void myNewMethod() { ... }
}

Because of the original hierarchy, FragmentActivity extends Activity, myNewMethod() should also be present in my library's FragmentActiviti

import android.support.v4.app.FragmentActivity;

public abstract class FragmentActiviti extends FragmentActivity {
    public void myNewMethod() { ... }
}

But this will lead to a duplication of code, which i do not want this happens. Is there a way to avoid this duplication?


Edit: Usage scenario

Activiti.java

public abstract class Activiti extends Activity {
    private int current_orientation = Configuration.ORIENTATION_UNDEFINED;  // ORIENTATION_UNDEFINED = 0

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        current_orientation = this.getResources().getConfiguration().orientation;
    }
    protected boolean isDevicePortrait() { return current_orientation == Configuration.ORIENTATION_PORTRAIT; }
}

FragmentActiviti.java

public abstract class FragmentActiviti extends FragmentActivity {
    /* This onCreate() can be omitted. Just putting here explicitly. */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    protected void someUtilsForFragments() { /* not used yet */ }
}

E_fragtest_06.java

public class E_fragtest_06 extends FragmentActiviti {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        System.out.printf(isDevicePortrait());      // this NOT WORK for now
    }
}

Edit 2: Try using Util class

i think using the Decorator Class would be the most nicest way to solve this problem (no duplication of code). But the Decorator Pattern is just a bit hard (or impossible) to apply on Android Activity scenario.

i try implementing @hazzik's approach, but i still experience some problems.

ActivityUtil.java

public abstract class ActivityUtil {
    private int current_orientation = Configuration.ORIENTATION_UNDEFINED;  // ORIENTATION_UNDEFINED = 0

    public void onCreate(Activity activity, Bundle savedInstanceState) {
        activity.onCreate(savedInstanceState);
        current_orientation = activity.getResources().getConfiguration().orientation;
    }
    public boolean isDevicePortrait() { return current_orientation == Configuration.ORIENTATION_PORTRAIT; }
}

Activiti.java

public class Activiti extends Activity {
    private ActivityUtil activityUtil;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        activityUtil.onCreate(this, savedInstanceState);
    }
    protected boolean isDevicePortrait() { return activityUtil.isDevicePortrait(); }
}

FragmentActiviti.java

public abstract class FragmentActiviti extends FragmentActivity {
    private ActivityUtil activityUtil;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        activityUtil.onCreate(this, savedInstanceState);
    }
    protected boolean isDevicePortrait() { return activityUtil.isDevicePortrait(); }
}

In ActivityUtil.onCreate(), activity.onCreate(savedInstanceState); is causing this compile error:

The method onCreate(Bundle) from the type Activity is not visible.

If i change Activity to Activiti:

public abstract class ActivityUtil {
    public void onCreate(Activiti activity, Bundle savedInstanceState) { ... }
    ...
}

It will lead to another compile error in FragmentActiviti.onCreate()'s activityUtil.onCreate():

The method onCreate(Activiti, Bundle) in the type ActivityUtil is not applicable for the arguments (FragmentActiviti, Bundle)

i understand why those errors occur. But i just don't know how to avoid them.


To thanks all the guys who have been contributing to this question, especially @flup for guiding me about the Decorator Pattern, @hazzik and @donramos for your extensive efforts, i m here posting

My enhanced Android's Activity and FragmentActivity classes.

If you are also developing Android applications, i hope my codes could help you guys in some ways :-)

ActivityCore.java

package xxx.android;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.res.Configuration;
import android.os.Bundle;

public final class ActivityCore {
    public interface ActivityCallbackInterface {
        public void onCreateCallback(Bundle savedInstanceState);
        public void onBeforeSaveInstanceState(Bundle outState);
        public void onSaveInstanceStateCallback(Bundle outState);
    }

    private final Activity activity;
    /**
     * This current_orientation variable should be once set, never changed during the object life-cycle.
     * But Activity.getResources() is not yet ready upon the object constructs.
     * That's why THIS CLASS is wholly responsible to maintain THIS VARIABLE UNCHANGED.
     */
    private int current_orientation = Configuration.ORIENTATION_UNDEFINED;  // ORIENTATION_UNDEFINED = 0

    public ActivityCore(Activity activity) { this.activity = activity; }

    public void onCreate(Bundle savedInstanceState) {
        ((ActivityCallbackInterface) activity).onCreateCallback(savedInstanceState);
        current_orientation = activity.getResources().getConfiguration().orientation;
    }

    public void onSaveInstanceState(Bundle outState) {
        /**
         * THIS is the best ever place i have found, to unload unwanted Fragments,
         * thus prevent re-creating of un-needed Fragments in the next state of Activity.
         *   (state e.g. Portrait-to-Landscape, or Landscape-to-Portrait)
         * 
         * The KEY is to do it BEFORE super.onSaveInstanceState()
         *   (my guess for this reason is, in super.onSaveInstanceState(),
         *    it saves the layout hierarchy, thus saved the Fragments into the Bundle also.
         *    Thus restored.
         *    Note that Fragments NOT IN LAYOUT, having ONLY TAGS, are also restored.)
         */
        ((ActivityCallbackInterface) activity).onBeforeSaveInstanceState(outState);
        ((ActivityCallbackInterface) activity).onSaveInstanceStateCallback(outState);
    }

    public int getCurrentOrientation() { return current_orientation; }

    public boolean isDevicePortrait() { return current_orientation == Configuration.ORIENTATION_PORTRAIT; }
    public boolean isDeviceLandscape() { return current_orientation == Configuration.ORIENTATION_LANDSCAPE; }
    public boolean isNewDevicePortrait() { return activity.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; }
    public boolean isNewDeviceLandscape() { return activity.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; }
    public boolean isPortrait2Landscape() { return isDevicePortrait() && isNewDeviceLandscape(); }
    public boolean isLandscape2Portrait() { return isDeviceLandscape() && isNewDevicePortrait(); }

    public String describeCurrentOrientation() { return describeOrientation(current_orientation); }
    public String getCurrentOrientationTag() { return getOrientationTag(current_orientation); }
    public String describeNewOrientation() { return describeOrientation(activity.getResources().getConfiguration().orientation); }
    public String getNewOrientationTag() { return getOrientationTag(activity.getResources().getConfiguration().orientation); }
    private String describeOrientation(final int orientation) {
        switch (orientation) {
            case Configuration.ORIENTATION_UNDEFINED: return "ORIENTATION_UNDEFINED";   // 0
            case Configuration.ORIENTATION_PORTRAIT: return "ORIENTATION_PORTRAIT";     // 1
            case Configuration.ORIENTATION_LANDSCAPE: return "ORIENTATION_LANDSCAPE";   // 2
            case Configuration.ORIENTATION_SQUARE: return "ORIENTATION_SQUARE";         // 3
            default: return null;
        }
    }
    @SuppressLint("DefaultLocale")
    private String getOrientationTag(final int orientation) {
        return String.format("[%d:%s]", orientation, describeOrientation(orientation).substring(12, 16).toLowerCase());
    }
}

Activity.java

package xxx.android.app;

import xxx.android.ActivityCore;
import xxx.android.ActivityCore.ActivityCallbackInterface;
import android.os.Bundle;

public abstract class Activity extends android.app.Activity implements ActivityCallbackInterface {
    private final ActivityCore activityCore;

    public Activity() { super(); activityCore = new ActivityCore(this); }

    @Override
    protected void onCreate(Bundle savedInstanceState) { activityCore.onCreate(savedInstanceState); }
    @Override public void onCreateCallback(Bundle savedInstanceState) { super.onCreate(savedInstanceState); }

    @Override
    public void onBeforeSaveInstanceState(Bundle outState) {}       // Optionally: let child class override
    @Override
    protected void onSaveInstanceState(Bundle outState) { activityCore.onSaveInstanceState(outState); }
    @Override public void onSaveInstanceStateCallback(Bundle outState) { super.onSaveInstanceState(outState); }

    public final int getCurrentOrientation() { return activityCore.getCurrentOrientation(); }

    public final boolean isDevicePortrait() { return activityCore.isDevicePortrait(); }
    public final boolean isDeviceLandscape() { return activityCore.isDeviceLandscape(); }
    public final boolean isNewDevicePortrait() { return activityCore.isNewDevicePortrait(); }
    public final boolean isNewDeviceLandscape() { return activityCore.isNewDeviceLandscape(); }
    public final boolean isPortrait2Landscape() { return activityCore.isPortrait2Landscape(); }
    public final boolean isLandscape2Portrait() { return activityCore.isLandscape2Portrait(); }

    public final String describeCurrentOrientation() { return activityCore.describeCurrentOrientation(); }
    public final String getCurrentOrientationTag() { return activityCore.getCurrentOrientationTag(); }
    public final String describeNewOrientation() { return activityCore.describeNewOrientation(); }
    public final String getNewOrientationTag() { return activityCore.getNewOrientationTag(); }
}

FragmentActivity.java

package xxx.android.support.v4.app;

import xxx.android.ActivityCore;
import xxx.android.ActivityCore.ActivityCallbackInterface;
import android.os.Bundle;

public abstract class FragmentActivity extends android.support.v4.app.FragmentActivity implements ActivityCallbackInterface {
    private final ActivityCore activityCore;

    public FragmentActivity() { super(); activityCore = new ActivityCore(this); }

    @Override
    protected void onCreate(Bundle savedInstanceState) { activityCore.onCreate(savedInstanceState); }
    @Override public void onCreateCallback(Bundle savedInstanceState) { super.onCreate(savedInstanceState); }

    @Override
    public void onBeforeSaveInstanceState(Bundle outState) {}       // Optionally: let child class override
    @Override
    protected void onSaveInstanceState(Bundle outState) { activityCore.onSaveInstanceState(outState); }
    @Override public void onSaveInstanceStateCallback(Bundle outState) { super.onSaveInstanceState(outState); }

    public final int getCurrentOrientation() { return activityCore.getCurrentOrientation(); }

    public final boolean isDevicePortrait() { return activityCore.isDevicePortrait(); }
    public final boolean isDeviceLandscape() { return activityCore.isDeviceLandscape(); }
    public final boolean isNewDevicePortrait() { return activityCore.isNewDevicePortrait(); }
    public final boolean isNewDeviceLandscape() { return activityCore.isNewDeviceLandscape(); }
    public final boolean isPortrait2Landscape() { return activityCore.isPortrait2Landscape(); }
    public final boolean isLandscape2Portrait() { return activityCore.isLandscape2Portrait(); }

    public final String describeCurrentOrientation() { return activityCore.describeCurrentOrientation(); }
    public final String getCurrentOrientationTag() { return activityCore.getCurrentOrientationTag(); }
    public final String describeNewOrientation() { return activityCore.describeNewOrientation(); }
    public final String getNewOrientationTag() { return activityCore.getNewOrientationTag(); }
}

Lastly, i really have to thanks you guys are being so so so helpful and keep updating the solving progress with me! You all are the key persons who make stackoverflow a perfect site for programmers. Should you spot any problems in my codes, or any rooms for improvements, please do not hesitate to help me again :-)

Some improvements?

It is because onBeforeSaveInstanceState() is implemented upon usage, all the three classes need to keep abstract. This leads to a duplication of the member variable current_orientation. If current_orientation could be put into class ActivityBase, or grouping it into somewhere else, it would be a lot nicer!

stupid me. i have fixed it :-)

Upvotes: 3

Views: 3093

Answers (4)

flup
flup

Reputation: 27104

You wish to add helper methods that help keep track of the orientation. I'd think this is not quite big enough to warrant the creation of a subclass.

Put them in a helper class instead:

public class OrientationHelper {
    private Activity activity;
    private int current_orientation;

    public OrientationHelper(Activity activity){
        this.activity = activity;
        orientation = Configuration.ORIENTATION_UNDEFINED;
    }

    public int getNewOrientation() { 
        return activity.getResources().getConfiguration().orientation;
    }

    // call this when you wish to update current_orientation
    public void updateOrientation() {
        current_orientation = getNewOrientation();
    }

    public int getCurrentOrientation() { 
        return current_orientation; 
    }

    public boolean isDevicePortrait() { 
        return current_orientation == Configuration.ORIENTATION_PORTRAIT; 
    }

    public boolean isDeviceLandscape() { 
        return current_orientation == Configuration.ORIENTATION_LANDSCAPE; 
    }

    public boolean isNewDevicePortrait() { 
        return getCurrentOrientation() == Configuration.ORIENTATION_PORTRAIT; 
    }

    public boolean isNewDeviceLandscape() { 
        return getCurrentOrientation() == Configuration.ORIENTATION_LANDSCAPE; 
    }

    public boolean isPortrait2Landscape() { 
        return isDevicePortrait() && isNewDeviceLandscape(); 
    }

    public boolean isLandscape2Portrait() { 
        return isDeviceLandscape() && isNewDevicePortrait(); 
    }

    public String describeCurrentOrientation() { 
        return describeOrientation(current_orientation); 
    }

    public String describeNewOrientation() { 
        return describeOrientation(getNewOrientation());
    }

    private String describeOrientation(int current_orientation) {
        switch (current_orientation) {
            case Configuration.ORIENTATION_UNDEFINED: 
                return "ORIENTATION_UNDEFINED";
            case Configuration.ORIENTATION_PORTRAIT: 
                return "ORIENTATION_PORTRAIT";
            case Configuration.ORIENTATION_LANDSCAPE: 
                return "ORIENTATION_LANDSCAPE";
            case Configuration.ORIENTATION_SQUARE: 
                return "ORIENTATION_SQUARE";
            default: return null;
        }
    }
}

In those activities that work with orientation (and only those), you can instantiate the OrientationHelper and call updateOrientation() in select places.

The other bit of code, that organizes the saving of the instance state, I would not put in a different class just so that you can reuse it. Because this is not where one would expect modifications to state saving to occur and therefore it might get overlooked. (It took me a bit of scrolling around to figure out what it's supposed to do.)

I think the most readable way to go about that is to write it out explicitly in each Activity where you use it.

One last thing to consider is that the Sherlock Actionbar already extends Activity. And rightly so, I think. But this means that you'll occasionally run into trouble if you extend Activity too.

Upvotes: 1

donramos
donramos

Reputation: 534

Your design problem is one of the issues addressed by the upcoming Java 8 virtual extensions. See URL below for more details:

http://java.dzone.com/articles/java-8-virtual-extension

In the meantime, there is no easy way. A decorator class will not work, instead implement a utility class that will be called by both of your classes:

EDITED BASED ON NEW INFO:

/** NOTE: cannot be abstract class **/
public class ActivitiBase {
   private int current_orientation = Configuration.ORIENTATION_UNDEFINED;  // ORIENTATION_UNDEFINED = 0

    private Activity activity;
    public void ActivitiBase(Activity activity) {
        this.activity = activity;
    }

   public void onCreate(Bundle savedInstanceState) {
      current_orientation = activity.getResources().getConfiguration().orientation;
   }

   public boolean isDevicePortrait() { return current_orientation == 
                      Configuration.ORIENTATION_PORTRAIT; }
   }    

   public void myNewMethod() { ... }
}

Activiti class:

public class Activiti extends Activity {
    private ActiviBase activitiBase;

    public Activiti() {
       activitiBase = new ActiviBase(this);
    }

    public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      activitiBase.onCreate(savedInstanceState);
    }

    public void myNewMethod() { 
       activitiBase.myNewMethod();
    }
}

FrameActiviti class:

public class FrameActiviti extends FrameActivity {
    private ActiviBase activitiBase;

    public FrameActiviti() {
       activitiBase = new ActiviBase(this);
    }

    public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      activitiBase.onCreate(savedInstanceState);
    }

    public void myNewMethod() { 
       activitiBase.myNewMethod();
    }
}

Upvotes: 0

hazzik
hazzik

Reputation: 13344

For my point of view the best solution here is to delegate logic to some class, let's call it CustomActivityLogic.

Also you need to create common interface (CustomActivity) for your activities if you want to access some data or methods of activity classes from your logic class.

To call protected virtual overridden methods there are two solutions:

  • call method of supper from overridden method
  • make a new method in subclass and call super method from this new method. Call new method from shared logic.

CustomActivity.java

public interface CustomActivity {
    void someMethod();
}

Activiti.java

import android.app.Activity

public class Activiti 
    extends Activity 
    implements CustomActivity {

    private CustomActivityLogic logic = new CustomActivityLogic();

    public void someMethod() { /***/ }

    public void myNewMethod() { logic.myNewMethod(this); }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        logic.onCreate(this, savedInstanceState); // call shared logic
        super.onCreate(savedInstanceState); // call super
    }
}

FragmentActivitii.java

import android.support.v4.app.FragmentActivity;

public class FragmentActivitii 
    extends FragmentActivity 
    implements CustomActivity {

    private CustomActivityLogic logic = new CustomActivityLogic();

    public void someMethod() { /***/ }

    public void myNewMethod() { logic.myNewMethod(this); }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        logic.onCreate(this, savedInstanceState); // call shared logic
        super.onCreate(savedInstanceState); // call super
    }
}

CustomActivityLogic.java

 public class CustomActivityLogic {
     public void myNewMethod(CustomActivity activity) { /*...*/ }
     public void onCreate(Activity activity, Bundle savedInstanceState) { 
         /* shared creation logic */
     }
 }

Approach with making onCreate available to call from outside via CustomActivity interface

CustomActivity.java

public interface CustomActivity {
    void someMethod();
    void onCreateSuper(Bundle savedInstanceState);
}

Activiti.java

import android.app.Activity

public class Activiti 
    extends Activity 
    implements CustomActivity {

    private CustomActivityLogic logic = new CustomActivityLogic();

    public void someMethod() { /***/ }

    public void myNewMethod() { logic.myNewMethod(this); }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        logic.onCreate(this, savedInstanceState); // call shared logic
    }

    public void onCreateSuper(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState); // call super
    }
}

FragmentActivitii.java

import android.support.v4.app.FragmentActivity;

public class FragmentActivitii 
    extends FragmentActivity 
    implements CustomActivity {

    private CustomActivityLogic logic = new CustomActivityLogic();

    public void someMethod() { /***/ }

    public void myNewMethod() { logic.myNewMethod(this); }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        logic.onCreate(this, savedInstanceState); // call shared logic
    }

    public void onCreateSuper(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState); // call super
    }
}

CustomActivityLogic.java

 public class CustomActivityLogic {
     public void myNewMethod(CustomActivity activity) { /*...*/ }
     public void onCreate(CustomActivity activity, Bundle savedInstanceState) { 
         /* shared creation logic */
         activity.onCreateSuper(savedInstanceState); // call-back super
     }
 }

Upvotes: 1

Porkbutts
Porkbutts

Reputation: 964

How about using the Decorator Pattern? Unfortunately, this will require you to delegate all of the existing methods, or whichever ones are necessary for your purpose.

public class ActivityDecorator extends Activity 
{
    private Activity RealActivity;
    public ActivityDecorator(Activity _realActivity)
    {
        RealActivity = _realActivity;
    }

    public void myNewMethod() { ... }  // this exposes the added/new functionality

    // unfortunately for old functionality you need to delegate
    public void oldMethod() { RealActivity.oldMethod(); }
}

However, once you've done this once for the ActivityProxy class, you can construct instances of ActivityDecorator with types that derive Activity such as FragmentActivity in your case. E.g.

ActivityDecorator decorator = new ActivityDecorator(new FragmentActivity());

Upvotes: 0

Related Questions