Search This Blog

Wednesday, September 29, 2010

Experience - Multiple Android Activities in a TabActivity

The problem that I had was when I started a new activity it covered up the Tabs on my TabActivity. I could put a single Activity into a Tab, but adding a second Activity resulted in losing view of the Tabs. Then I learned about ActivityGroups which have a LocalActivityManager. I realized it was possible to have multiple Activities within an ActivityGroup after all this is what a TabHost does. I then realized if I set the content of each Tab to an ActivityGroup I could start multiple Activities in each Tab. I developed the below ActivityGroup.

import java.util.ArrayList;

import android.app.Activity;
import android.app.ActivityGroup;
import android.app.LocalActivityManager;

import android.content.Intent;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.Window;

/**
* The purpose of this Activity is to manage the activities in a tab.
* Note: Child Activities can handle Key Presses before they are seen here.
* @author Eric Harlow
*/
public class TabGroupActivity extends ActivityGroup {

private ArrayList<String> mIdList;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (mIdList == null) mIdList = new ArrayList<String>();
}

/**
* This is called when a child activity of this one calls its finish method.
* This implementation calls {@link LocalActivityManager#destroyActivity} on the child activity
* and starts the previous activity.
* If the last child activity just called finish(),this activity (the parent),
* calls finish to finish the entire group.
*/
@Override
public void finishFromChild(Activity child) {
LocalActivityManager manager = getLocalActivityManager();
int index = mIdList.size()-1;

if (index < 1) {
finish();
return;
}

manager.destroyActivity(mIdList.get(index), true);
mIdList.remove(index);
index--;
String lastId = mIdList.get(index);
Intent lastIntent = manager.getActivity(lastId).getIntent();
Window newWindow = manager.startActivity(lastId, lastIntent);
setContentView(newWindow.getDecorView());
}

/**
* Starts an Activity as a child Activity to this.
* @param Id Unique identifier of the activity to be started.
* @param intent The Intent describing the activity to be started.
* @throws android.content.ActivityNotFoundException.
*/
public void startChildActivity(String Id, Intent intent) {
Window window = getLocalActivityManager().startActivity(Id,intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP));
if (window != null) {
mIdList.add(Id);
setContentView(window.getDecorView());
}
}

/**
* The primary purpose is to prevent systems before android.os.Build.VERSION_CODES.ECLAIR
* from calling their default KeyEvent.KEYCODE_BACK during onKeyDown.
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
//preventing default implementation previous to android.os.Build.VERSION_CODES.ECLAIR
return true;
}
return super.onKeyDown(keyCode, event);
}

/**
* Overrides the default implementation for KeyEvent.KEYCODE_BACK
* so that all systems call onBackPressed().
*/
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
onBackPressed();
return true;
}
return super.onKeyUp(keyCode, event);
}

/**
* If a Child Activity handles KeyEvent.KEYCODE_BACK.
* Simply override and add this method.
*/
@Override
public void onBackPressed () {
int length = mIdList.size();
if ( length > 1) {
Activity current = getLocalActivityManager().getActivity(mIdList.get(length-1));
current.finish();
}
}
}


I got caught up trying to figure out if and how LocalActivityManager handled stepping back up the view hierarchy. After viewing the note on the LocalActivityManager.dispatchCreate(bundle state) which states "Note: This does not change the current running activity, or start whatever activity was previously running when the state was saved. That is up to the client to do, in whatever way it thinks is best."
I decided to add back tracking to the ActivityGroup. I attempted to make it behave the same as Activities outside an ActivityGroup by only needing to call finish() on an Activity and the previous Activity will be restarted or recreated.
I have also added the ability for the ActivityGroup to manage the back button being pressed. Android OS pre 2.0 handles things differently than the newer interface. A reference to this can be found here under Key Events Executed on Key-Up. I have tested the code on a device with OS 1.6 and the emulator with OS 2.1. Note: Child Activities like a ListActivity may handle the back button just override the appropriate methods for your OS version, and call onBackPressed() for the (parent) ActivityGroup. An example would look like this for OS 2.1.

@Override
public void onBackPressed() {
TabGroupActivity parentActivity = (TabGroupActivity)getParent();
parentActivity.onBackPressed();
}


The following section explains how I used this ActivityGroup. The first thing I did was subclass this ActivityGroup like this.

import android.content.Intent;
import android.os.Bundle;

public class TabGroup1Activity extends TabGroupActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
startChildActivity("OptionsActivity", new Intent(this,OptionsActivity.class));
}
}


I added this TabGroup1Activity into my TabActivity like this.

TabHost tabHost = getTabHost();

tabHost.addTab(tabHost.newTabSpec("tab1")
.setIndicator("MESSAGES")
.setContent(new Intent(this, TabGroup1Activity.class)));


Then any Activity that you want to start in the ActivityGroup can be done in a way similar to this.

Intent frequentMessages = new Intent(getParent(), FrequentMessageActivity.class);
TabGroupActivity parentActivity = (TabGroupActivity)getParent();
parentActivity.startChildActivity("FrequentMessageActivity", frequentMessages);


And that is how I added multiple Activities with a back tracking ability to a Tab of a TabActivity. Remember to always add Activities to your AndroidManifest.xml. A sample project for this article can be found here.