Sometimes you have a lot of activities in your app, each having something in common with others. The usual way to avoid code duplication in this case is to have something like an AbstractActvity superclass (or a hierarchy of superclasses), abstracting out commonalities.
Unfortunately, there are cases when this appreoach does not work. Consider this:
- some activities need to be able to display progress dialog while doing an API request
- some activities need to require user to sign in before letting him do anything else
- some activities need to display an error dialog of the same type
- some activities need to have title that is passed in an Intent’s extra
So what would you do if activity A needed 1 and 2, activity B needed 2 and 3, activity C needed 3 and 4, etc? Since Java has no multiple inheritance, code duplication seems unavoidable.
Yet, there is a way to extract commonalities not via inheritance, but via composition.
What if each of these 1-4 commonalities was fully described by a separate class (I call it an “aspect”), encapsulating required behavior? Then, all that activities would need to do is to just hook up the aspects it needs in onCreate (and other callback methods), without a need to extend a superclass.
For example, ProgressDialogAspect may look like this:
public class ProgressDialogAspect {
private final Activity activity;
private final int dialogId;
public ProgressDialogAspect(final Activity activity, final int dialogId) {
this.activity = activity;
this.dialogId = dialogId;
}
public ProgressDialog createDialog() {
final ProgressDialog dialog = new ProgressDialog(this.activity);
dialog.setMessage(this.activity.getString(R.string.please_wait));
return dialog;
}
public void showProgress() {
this.activity.showDialog(this.dialogId);
}
public void hideProgress() {
this.activity.dismissDialog(this.dialogId);
}
}
Then, hooking it up in an Activity will look like this:
private static final int DIALOG_PROGRESS = 0;
private ProgressDialogAspect progressDialogAspect;
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.progressDialogAspect = new ProgressDialogAspect(this, DIALOG_PROGRESS);
}
@Override
protected Dialog onCreateDialog(final int id) {
switch (id) {
case DIALOG_PROGRESS: {
return this.progressDialogAspect.createDialog();
}
}
}
public void onSomething(View button) {
this.progressDialogAspect.showProgress();
}
Another example: an aspect requiring user to log in before using an Activity:
public class RequireLoginAspect {
private final Activity activity;
private final int requestCode;
public RequireLoginAspect(final Activity activity, final int requestCode) {
this.activity = activity;
this.requestCode = requestCode;
}
public boolean checkLogin() {
final String token = Preferences.get(this.activity).getString(Preferences.AUTH_TOKEN, null);
if (token == null) {
this.activity.startActivityForResult(new Intent(this.activity, LoginActivity.class), this.requestCode);
return false;
}
return true;
}
public void processLoginActivityResultCode(final int resultCode) {
switch (resultCode) {
case Activity.RESULT_OK: {
break;
}
case Activity.RESULT_CANCELED: {
this.activity.finish();
break;
}
}
}
}
This aspect could be hooked up in this way:
private static final int REQUEST_CODE_LOGIN = 0;
private RequireLoginAspect requireLoginAspect;
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.requireLoginAspect = new RequireLoginAspect(this, REQUEST_CODE_LOGIN);
}
protected void onResume() {
super.onResume();
if (!this.requireLoginAspect.checkLogin()) {
return;
}
}
protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
switch (requestCode) {
case REQUEST_CODE_LOGIN: {
this.requireLoginAspect.processLoginActivityResultCode(resultCode);
return;
}
}
}
Also, note that aspects do not have their own dialog and request codes – this lets us to not worry about clashes between aspect’s and Activity’s codes . Also, aspects written in this way may be re-used in other apps with no code change (though for complete re-usability resource IDs need to be passed into aspect as well as request/dialog codes).
Hope this helps.