Intent intent = new Intent(this, SlideshowActivity.class); intent.putExtra("videoUri", videoUri); intent.putExtra("imageFileNames", images); intent.putExtra("selectedSong", song); startActivity(intent);And here is code from SlideshowActivity to retrieve this data:
Intent intent = getIntent(); ArrayList<String> images = intent.getStringArrayListExtra("imageFileNames"); Song song = intent.getParcelableExtra("selectedSong"); // custom Parcelable class Uri videoUri = intent.getParcelableExtra("videoUri");There is an obviously ugly issue here. Both Activities need to know the keys (imageFileNames, selectedSong, videoUri) to use to put/get from the Intent's extras. This is such a common thing in Android, even in the framework, that there is a simple pattern for dealing with it. Just use public constants declared in the class that will use the Intent extras:
public class SlideshowActivity extends Activity{ public static final String EXTRA_IMAGE_FILE_NAMES = "imageFilesNames"; public static final String EXTRA_SELECTED_SONG = "selectedSong"; public static final String EXTRA_VIDEO_URI = "videoUri"; ... Intent intent = getIntent(); ArrayList<String> images = intent.getStringArrayListExtra(EXTRA_IMAGE_FILE_NAMES); // etc.And then of course in the calling class you would now have:
intent.putExtra(SlideshowActivity.EXTRA_IMAGE_FILES_NAMES, images): // etc.Problem solved, right? Well not exactly. You still don't know what the types of the extras should, and you do not know if a particular extra is required or optional. Documenting your code can help with this:
public class SlideshowActivity extends Activity{ /** * An ArrayList of Strings. Required. */ public static final String EXTRA_IMAGE_FILE_NAMES = "imageFileNames"; ... }So we put the type information and wether the field is required or not as a comment. Wait, doesn't this feel more like Ruby or Python instead of Java? Isn't there some way to use the language syntax and compiler to state this information in a better way instead of relying on code comments? That's where the Static Starter Pattern kicks in:
public class SlideshowActivity extends Activity{ public static void start(ArrayList<String> images, Song song, Uri videoUri, Context ctx){ Intent intent = new Intent(ctx, SlideshowActivity.class); intent.putExtra(EXTRA_IMAGE_FILE_NAMES, images); intent.putExtra(EXTRA_SELECTED_SONG, song); intent.putExtra(EXTRA_VIDEO_URI, videoUri); ctx.startActivity(intent); } ... }From this method signature, I know the types of all of the extras and I know that they are all required. If I wanted to make one of them optional, then I could simply overload the start method with only two of the parameters, removing the optional parameter. Of course, somebody could bypass this and still use an Intent.
Now this is not the best pattern for every Activity. The most obvious example is if you want your Activity to be started by other applications. Another application won't be able to invoke a static method. You will have to use the action/Intent-filter path for this. Of course this is pretty rare, how many of your Activities are meant to be started by other apps? Even if this is the case, you can still use the pattern for use within your app.
This pattern is also not limited to Activities. You can do it with Services as well, especially IntentServices:
public class AwesomeService extends IntentService{ public static void start(String someString, int someInt, Context ctx){ Intent intent = new Intent(ctx, AwesomeService.class); intent.putExtra(EXTRA_SOME_STRING, someString); intent.putExtra(EXTRA_SOME_INT, someInt); ctx.startService(intent); } }If your IntentService handles multiple actions, this can be built into the pattern:
public class AwesomeService extends IntentService{ public static startUploadFile(String someFileName, Context ctx){ Intent intent = new Intent(ctx, AwesomeService.class); intent.setAction(ACTION_UPLOAD_FILE); // ACTION_UPLOAD_FILE is a constant intent.putExtra(EXTRA_SOME_FILE_NAME, someFileName); ctx.startService(intent); } }You get the idea. You could extend this to BroadcastReceivers as well. I discussed this pattern a bit with my Android in Practice co-author, Charlie Collins. He liked it too, and pointed out that others have used it as well. Personally, I'm not a fan of static methods or of passing around the Context like that, but the positives seem to outweigh the negatives. What do you think? Useful? Over-engineered?
Smart idea!
ReplyDeleteThis is exactly the kind of practice promoted by Effective Java, using the compiler to avoid mistakes, and I think it works well.
ReplyDeleteI like pass Intent created like 'new Intent(this, SomeActivity.class)' and pass to static method that will just add extras. With this modified intent you can call startActivity or whatever you want.
ReplyDeletei just discovered that google does this in their own contacts app. take a look at the buildIntent method here (https://github.com/android/platform_packages_apps_contacts/blob/master/src/com/android/contacts/activities/PhotoSelectionActivity.java) for example.
ReplyDeleteFor an even more automated way to do this, have a look at Android Annotations, and its IntentBuilder: https://github.com/excilys/androidannotations/wiki/HowItWorks#StartingAnAnnotatedActivity
ReplyDeleteStarter pattern does not run until I set flag to intent as follows
ReplyDeleteintent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
otherwise it gives exception as follows
android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
Why?