androiddevblog

An Android developer at Grooveshark

A simple introduction to the Android framework

with 7 comments

Some of the more common questions that I see posted to various Android development forums are from developers curious about, or new to the platform, and looking for a good, light introduction to it all. This is my best attempt at a thorough, simple summary.

An Android application should be thought of as a package comprised of separate, loosely-coupled components connected at runtime, not as a single executable. These components work together by responding to events, or Intents, that are broadcast by other components included in an application, or by the Android system. Each Android app has a “manifest” file, AndroidManifest.xml, that lists each component that an app includes, and which Intents, if any, will trigger these components to do work.

Intents

In its simplest form, an Intent contains an “action” string, which is usually represented as a Java package name prefixed to something like “action.COMMAND_NAME.” An Intent action might be “com.example.action.DOWNLOAD_LATEST_WEATHER,” for example. Many framework components are capable of broadcasting an Intent, and likewise many framework components are capable of registering to receive these broadcasts. To be notified when a particular Intent is broadcast, an application can create a BroadcastReceiver that listens for the broadcast of a specific Intent action. Intents are really just a way of connecting callbacks.

An Intent can also contain a Map of key-value pairs that is sent along as “extras” when the Intent is broadcast. Like the value 32601 for the extra “userZipcode.”

Services and BroadcastReceivers

These separate framework components work together to support Android’s multitasking model because – among other things – they allow parts of an application to do work even when the user is doing other things, like checking his email or browsing a web page.

To give an example, BroadcastReceivers allow apps to be notified when the phone’s SD card is mounted, or when a text message is received, even when the app, or package, to which the component belongs is not in the foreground. Likewise, Services allow an app to perform long-running tasks in the background, like playing music. Services and BroadcastReceivers can be combined to create an application that performs a task triggered by a system event, like downloading the latest weather report when the phone’s location changes.

Note that Services allow applications to do work in the background only in the sense that an application is doing work without any of its Activities being open and visible to the the user in the foreground, though this work is still being done on the app’s main, or UI, thread. It’s important to understand that all framework components, and almost all SDK-provided framework component instances, run on the main thread. One exception that I’m aware of is IntentService, which is useful if you want to perform a task in the background, on a background thread.

Activities

As alluded to, Activities are the parts of an app that users can see and with which they may interact. An Activity has a view which is built from xml that you create, and it can contain widgets like buttons, text input fields, date pickers, and other common user-interface components. It also contains these views’ click-listeners and any other supporting code. Activities allow an application to show AlertDialogs, ProgressDialogs, Toast notifications, and other things that can be used to grab a user’s attention or prompt him for input. Using callbacks that you may override, Activities can have menus that are activated by the phone’s “menu” hard-key, and list items can have long-press, or context, menus.

ContentProviders

ContentProviders are used to make an application’s data available to other apps on a user’s phone. A scheduling app might make its appointments available to be read by other applications, using a ContentProvider.

Putting it together

You can use as many of these components in your application, or package, as you like: there’s nothing in the framework that restricts you to using one single Activity, or even one single Service, and there’s nothing in the framework that requires you to use any components that you don’t need. For example, the Grooveshark app has 31 Activities, 3 Services, many BroadcastReceivers, but no ContentProviders.

As a person uses an Android app, they navigate from Activity to Activity, just as they might browse a series of pages on a web site. And just like URL arguments or forms are used to send information from page-to-page, Android apps use Intent extras to send things between Activities. Just as a person might leave a web site at any time, a user of an Android app might leave, or pause, the app at any time by pressing the phone’s “home” hard key. The framework provides callbacks that you may override in your Activity instances, letting you save and resume state when this happens.

Undoubtedly, parts of the Android framework documentation can be overwhelming and confusing, but the basics don’t have to be. The SDK documentation has improved immensely over the past year, and I’ve no doubt that it will continue to get better going forward.

For a more in-depth discussion of framework components and their usage, the SDK Application Fundamentals doc is an absolutely fantastic resource, which you can read at http://developer.android.com/guide/topics/fundamentals.html. If you’re still curious about Android development, read through this a few times and really be sure that you’ve got a good understanding of the basics before you move on to any advanced concepts.

Good luck writing apps!

Written by Skyler Slade

March 4, 2011 at 1:18 am

Posted in Uncategorized

Sharing complex Dialog interactions across multiple Activities

with 8 comments

One of the more common problems that I have run into in Android application development is how to reuse complex, multi-step dialog actions across multiple activities while avoiding code duplication.

In the Grooveshark Android app, several instances of Activity and ListActivity give the user the option to add a song to a playlist. Choosing to add to a playlist starts a multi-step series of Dialog actions: the user is asked if they want to add the song to an existing playlist, or to create a new playlist. If the user chooses to add to an existing playlist, they’re presented with a radio-list of their playlists, otherwise they’re asked to enter a name for their new playlist. After entering a name and pressing save, a ProgressDialog is shown while a network request is made to create the playlist through Grooveshark’s API. If the user chooses to add a song to an existing playlist, a network request is then made to the Grooveshark API to add the song to the playlist, spawning a ProgressDialog.

As a rule, when making any network requests from the app, a “cancelable” indeterminate ProgressDialog must be shown. If the user cancels the operation by pressing their phone’s “back” hard-key, the network request should be canceled immediately, and any resources freed. Additionally, all dialogs must respond to application configuration changes—like an orientation change—and any ongoing network requests must survive these configuration changes, too.

This “add to playlist” interaction requires eight dialogs:

  1. “Create new playlist or add to existing” AlertDialog
  2. An AlertDialog for entering a new playlist name, or
  3. A ProgressDialog shown when loading a user’s list of playlists, and,
  4. An AlertDialog with single-choice items for picking a playlist from the user’s list of playlists
  5. A ProgressDialog shown when creating a new playlist and saving a song to this playlist, or
  6. A ProgressDialog shown when adding a song to an existing playlist
  7. An AlertDialog shown when creating a new playlist fails, or
  8. An AlertDialog shown when adding a song to an existing playlist fails

This process uses three separate AsyncTask instances:

  • Loading the logged-in user’s complete list of playlist names either from a network request or from the local database
  • Creating a new playlist and adding a song to it
  • Adding a song to an existing playlist

This is a complex set of interactions that totals around 600 lines of code, and this set of interactions needs to be launched from nearly every Activity in the Grooveshark application.

Most of these activities are instances of ListActivity—the user’s list of favorite songs, popular songs, cached songs, playlist songs, song search results, etc.—so this Dialog and AsyncTask code could live in a base ListActivity class from which all other list activities derive. The Grooveshark application does make use of a base ListActivity class that provides a common context and option menu, but not all activities needing the “Add to Playlist” feature are instances of ListActivity but not all activities needing the “Add to Playlist” feature are instances of ListActivity;  “NowPlaying”—our player and queue Activity—is a notable example.

Composition could be used to give each Activity needing “Add to Playlist” functionality these dialogs and AsyncTask instances, but this would require a litany of cumbersome callbacks to connect the host activity’s lifecycle callbacks to our proxy object—callbacks like onCreateDialog() to create our eight dialogs, and onRetainNonConfigurationInstance() to keep a reference to any active AsyncTask instances across Activity configuration changes.

My ideal solution to this problem:

  • Does not unnecessarily duplicate code
  • Does not use object inheritance
  • Does not use object composition
  • Encapsulates all Dialog creation and display code
  • Encapsulates all relevant AsyncTask code
  • Can be launched from any application Activity
  • Can respond to all Activity lifecycle methods

The solution I’ve found that fulfills all of our requirements uses a single transparent Activity instance to handle all related Dialog steps. This host Activity contains all Dialog code in onCreateDialog()—and onPrepareDialog(), if necessary—and encapsulates all of its network requests and database interactions in member AsyncTask instances. I call this the “transparent dialog-host activity” pattern, and the Grooveshark app uses this for its “Add to Playlist” feature, and in other places.

Transparent Dialog-Host Activity

I’ve put together a demo application to demonstrate how to use this pattern. It’s a simple to-do list app that let’s you create and edit to-do items. The demo app has two ListActivity instances and one transparent Activity instance that hosts its dialogs. We’ll walk through the important parts, and you can grab the entire source from the GitHub repo.

Getting Started

First create the Activity to contain your Dialog instances. In the demo application I linked to, this is the DialogTasks.java file. All dialogs are activity-managed, which means that on configuration change, Android handles dismissing and reopening any dialogs that were open before the configuration changed occurred.

It is always best to use managed dialogs; do not create and show dialogs on your own, always use onCreateDialog(), onPrepareDialog(), showDialog() and dismissDialog() or removeDialog(). Showing and dismissing dialogs without knowledge of and the ability to respond to lifecycle and configuration changes can be extremely error-prone and will crash your entire application if you do it wrong.

In DialogTasks.java we first define our dialog id constants:

private static final int EDIT_NAME_DIALOG = 0;
private static final int EDIT_DATE_DIALOG = 1;
private static final int CREATE_NEW_DIALOG = 2;
private static final int SAVING_DIALOG = 3;
private static final int DATE_PICKER_DIALOG = 4;
private static final int CONFIRM_DELETE_DIALOG = 5;
private static final int DELETING_DIALOG = 6;

And we define our dialogs in our activity’s onCreateDialog() method. Our demo program uses seven dialogs: a DatePickerDialog for choosing your to-do item’s due date, four AlertDialog instances for collecting input and showing a confirmation prompt, and two ProgressDialog instances that we show when saving changes.

/**
 * Lifecycle method invoked to create a dialog shown via showDialog()
 * NOTE: we override the older, deprecated method so that we can work
 * on older sdk versions.
 */
@Override
public Dialog onCreateDialog(int which)
{
    if (which == EDIT_NAME_DIALOG) {
        final View dialogView = getLayoutInflater().inflate(R.layout.new_task_dialog, null);
        AlertDialog dialog = new AlertDialog.Builder(this)
            .setView(dialogView)
            .setTitle(R.string.edit_task_title)
            .setPositiveButton(R.string.save, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int id)
                {
                    String name = ((EditText) dialogView.findViewById(R.id.task_name_edit)).getText().toString();
                    ToDoList.ToDoItem toDoItem = new ToDoList.ToDoItem(name,
                            currentToDoItem.year,
                            currentToDoItem.monthOfYear,
                            currentToDoItem.dayOfMonth);
                    ToDoList.getInstance().remove(currentToDoItem);
                    ToDoList.getInstance().add(editedToDoItem);
                    showDialog(SAVING_DIALOG);
                    // Normally this would start an AsyncTask to make a network request or
                    // to write to a local database, but for this demo I'm using a Handler
                    // on the main thread to simulate the delay of sending a network request
                    // and waiting for its response.
                    handler.postDelayed(new Runnable() {
                        @Override
                        public void run()
                        {
                            Toast.makeText(DialogTasks.this, R.string.task_updated,
                                    Toast.LENGTH_SHORT).show();
                            setResult(RESULT_OK);
                            finish();
                        }
                    }, 3000);
                }
            })
            .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int id)
                {
                    setResult(RESULT_CANCELED);
                    finish();
                }
            }).create();
        return dialog;
    }
}

Rather than post the entire onCreateDialog() implementation from the demo, which is a bit lengthy, I’ve only posted an excerpt so that I can point out a few key things. Note that for both the “save” and “cancel” buttons, in their on-click listeners we set an activity result code via setResult() and we call finish() to end our containing Activity. DialogTasks.java can be launched with startActivityForResult(), as it is from our demo’s ToDoListActivity.java, and in this instance, the result code from DialogTasks.java is used to update its list.

It’s important to finish() your dialog activity from any “last step” of your dialog actions. If you do not invoke finish() when the last Dialog closes, a user of your application will be able to see whatever activity instance was open before the dialog activity launched, but they will not be able to interact with it–it will not receive any touch or menu events until the phone’s “back” hard-key is pressed.

DialogTasks.java supports four work-flows: creating a new to-do item, editing its name, editing its due date, and deleting a to-do item. To pick which work-flow, or task, to start, we use extras in the Intent used to start our activity.

These Intent extras are defined in DialogTasks.java

/**
 * Extra sent to create a new ToDo item
 */
public static final String CREATE_TODO_EXTRA = "createToDo";
/**
 * Extra sent to edit an existing ToDo item's name
 */
 public static final String EDIT_TODO_NAME_EXTRA = "editToDoName";
/**
 * Extra sent to delete an existing ToDo item
 */
public static final String DELETE_TODO = "deleteToDo";

And DialogTasks.java can be launched from any other Activity like so:

Intent i = new Intent(this, DialogTasks.class);
i.putExtra(DialogTasks.CREATE_TODO_EXTRA, true);
i.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
startActivity(i);

Note that when starting our dialog-host activity, we disable Android’s transition animation with the Intent.FLAG_ACTIVITY_NO_ANIMATION flag. Without disabling the transition animation, the first Dialog opened from our transparent Activity would appear to open with an activity’s animation rather than the usual Dialog animation. On stock Android 2.x—API level 5—the dialog would appear as it slid-in from the right. This flag is only available on API levels 5 and higher, but luckily on stock Android versions prior to API level 5, the Activity transition animation is the same zoom-in effect used when opening activities, so the extra animation isn’t noticeable.

In DialogTasks.java we check which extras are set, and start the specified work-flow by displaying the first dialog of the task.

@Override
public void onCreate(Bundle inState)
{
    super.onCreate(inState);

    Bundle extras = getIntent().getExtras();
    if (extras != null) {
        currentToDoItem = extras.getParcelable(ToDoList.ToDoItem.TODO_EXTRA);
        if (extras.containsKey(CREATE_TODO_EXTRA)) {
            showDialog(CREATE_NEW_DIALOG);
        } else if (extras.containsKey(EDIT_TODO_NAME_EXTRA)) {
            showDialog(EDIT_NAME_DIALOG);
        } else if (extras.containsKey(DELETE_TODO)
                && extras.containsKey(ToDoList.ToDoItem.TODO_EXTRA)) {
            showDialog(CONFIRM_DELETE_DIALOG);
        } else {
            finish();
        }
        /*
         * Overwrite our Intent's original extras so that on configuration
         * change we do not create duplicate, overlapping dialogs. Recall that
         * on configuration change, Android will re-create and show any
         * already-open managed dialogs.
         */
        Intent intent = getIntent();
        if (intent != null) {
            intent.replaceExtras((Bundle) null);
        }
    }
}

Note that we replace our activity’s getIntent() extras with an empty Bundle. As mentioned in the source code comments, it’s important to replace these extras because on configuration change, the original Intent is re-delivered to our new Activity instance.

Recall that when using activity-managed dialogs, Android handles removing and re-displaying any open Dialog instances on configuration change. If we do not replace our activity’s Intent extras, when this event occurrs, overlapping dialogs will be shown: both the initial dialog opened in onCreate() by one of our Intent extras, and the re-displayed Dialog that was previously closed by Android.

Finally we make our Activity transparent and and remove its title bar by specifying styling options in AndroidManifest.xml.

<activity android:name="DialogTasks"
          android:theme="@android:style/Theme.Translucent.NoTitleBar"
          android:noHistory="true" />

A Caveat

So far the only thing that doesn’t work as you would expect it to when displaying dialogs in this way is that the underlying activity’s options menu is not shown when the phone’s “menu” hard-key is pressed. This is because the foreground, transparent Activity has focus, so all key events are delivered to it. This hasn’t yet been a problem in the Grooveshark app.

When to use this pattern

Any time you have a set of dialogs that are part of a “wizard-like” process–moving from step 1.) to step 2.), collecting user input and showing different dialogs in response to user input—if this functionality is needed from multiple Activity instances, it’s a good place to use transparent dialog-host activities. If you only need to show these dialogs from a single place in your app, this pattern isn’t a good fit. Additionally if you need to display only a single Dialog from multiple Activity instances, you should bite the bullet and define all of your Dialog code from within each individual activity’s onCreateDialog() method.

Don’t forget to grab the source code and have fun!

Written by Skyler Slade

February 9, 2011 at 2:31 am

Posted in Demos

Follow

Get every new post delivered to your Inbox.