Monday, March 8, 2010

Modifying an Alert Dialog's List Items after Creation

Category: UI
Subcategory: AlertDialog, ListView
Android Platform: 1.5, Google APIs 3
Referenced Classes: android.app.AlertDialog, android.widget.ListAdapter, android.widget.ListView

There are cases when you need to change the text of an Alert Dialog's List Items, after the Alert Dialog has already been created. This is typical when working with the Dialog lifecycle of an Activity (i.e. Activity.onCreateDialog(…), Activity.onPrepareDialog(…)). Take the following example:
code snippet
protected Dialog onCreateDialog(int id)
{


final CharSequence[] items = {"Red", "Green", "Blue"};

AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Pick a color");
builder.setItems(items, new DialogInterface.OnClickListener() {
    public void onClick(DialogInterface dialog, int item) {
        Toast.makeText(getApplicationContext(),
items[item], Toast.LENGTH_SHORT).show();
    }
});
AlertDialog dropdown = builder.create();


}
Suppose, following some user action in the Activity UI, you want to append text to the first list item of the drop down. So, instead of displaying "Red", the first list item should read "Red {actionResultValue}". First, you need to modify the above code, accordingly:
code snippet
protected Dialog onCreateDialog(int id)
{


final CharSequence[] items = {
new StringBuilder(),
"Green",
"Blue"}; // or any non-immutable CharSequence


}
Now, when Activity.onCreateDialog(…) is called, the first list item is a pseudo placeholder. Next, set the text of the first list item in Activity.onPrepareDialog(…) with the following:
code snippet
protected void onPrepareDialog(int id, Dialog dialog)
{


String actionResultValue = ...
ListView dropdown= ((AlertDialog) dialog).getListView();
ListAdapter dropdownAdapter= dropdown.getAdapter();
//
StringBuilder listItem0Text=
(StringBuilder) dropdownAdapter.getItem(0);
listItem0Text.delete(0, listItem0Text.length());
listItem0Text.append("Red " + actionResultValue);
//
dropdown.invalidateViews();


}
Note, the call to dropdown.invalidateViews(). Without this, the text changes will not display.

Sunday, March 7, 2010

Using the Maps External Library on an Android Device

Category: Production
Subcategory: Google APIs Add-On, Maps External Library
Android Platform: 1.5, Google APIs 3

During development, configuring an Android Virtual Device (AVD) to use the Maps External Library, is as simple as setting the target to Google APIs API Level 1.x. However, when deploying to production, you are entirely dependent on the Android Device's system image. Consequently, you can spend months developing a Map-based app, that won't run on a device that you are targeting. I was unable to find a published list of supporting Android Devices. Furthermore, the Google APIs Add-On Site states:

"The Maps external library is not part of the standard Android library, so it may not be present on some compliant Android-powered devices. Similarly, the Maps external library is not included in the standard Android library provided in the SDK."

In an attempt to ensure my app would run independent of any device's system image, I tried to bundle the com.google.android.maps.jar directly into my app. I removed the "<uses-library android:name="com.google.android.maps" /> from my AndroidManifest.xml file, and placed the maps.jar directly into my app's lib directory. After succeeded on the build phase I deployed to the AVD. However, an exception was thrown, with some message of "Stub Only". Feeling somewhat discouraged, I looked around for any reasonably priced--preferably free or open-source--third-party Android Map SDKs or libraries. I found two notable ones: Ericsson Labs Mobile Maps and Nutiteq MGMaps Lib SDK. After considering these alternatives, and accounting for the time and effort already invested using the Maps External Library, I decided to go another direction.

Sticking with the Maps External Library, I would compile my own list of supporting Android Devices and only provide app support accordingly. Besides deploying the app to the device, another way to ensure that an Android Device supports the Maps External Library, is to connect to it, using the adb tool. Navigate to the /system/framework/ folder and verify that the com.google.android.maps.jar is there. I suspect that any device running the Google Maps App, would have this jar--I cannot confirm this. Thus far, my list is rather small, but I hope to add more. Please, let me know if you have tested or verified other devices that are not already on this list. I'll be happy to add them.

Android Devices with Maps External Library Google APIs 1.5 Level 3 Support

  • Motorola Droid
  • HTC Eris

Sunday, January 31, 2010

Setting width of Child View using Percentage

Category: UI
Subcategory: LinearLayout, Style
Android Platform: 1.5, Google APIs 3
Referenced Classes: android.widget.LinearLayout, android.widget.TextView

There have been cases where I needed to the set the width of a Child View using percentage, as opposed to dimension value (i.e. pixel). As an example, I wanted three TextViews of equal width to be contained in a horizontal LinearLayout, filling an entire row. Consequently, each TextView would need to have a width of 33%. In HTML, a simple way to do this would be:
code snippet
<table width="100%">
<tr>
<td width="33%">...</td>
<td width="33%">...</td>
<td width="33%">...</td>
</tr>
...
...
In Android, this can be done by using android:weightSum, android:layout_weight, and android:layout_width:
code snippet
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:orientation="horizontal"
android:weightSum="1.0">
<TextView
android:id="@+id/textbox1"
android:layout_height="wrap_content"
android:layout_weight=".33"
android:layout_width="0dip"
android:textSize="12sp" />
<TextView
android:id="@+id/textbox2"
android:layout_height="wrap_content"
android:layout_weight=".33"
android:layout_width="0dip"
android:textSize="12sp" />
<TextView
android:id="@+id/textbox3"
android:layout_height="wrap_content"
android:layout_weight=".33"
android:layout_width="0dip"
android:textSize="12sp" />
</LinearLayout>
Note, that android:layout_width must be set to 0 for android:layout_weight to be effective.

Tuesday, January 19, 2010

Centering Title of Window

Category: UI
Subcategory: WindowTitle, Style
Android Platform: 1.5, Google APIs 3
Referenced Classes: android.app.Activity, android.view.Gravity, android.view.View, android.widget.FrameLayout, android.widget.LinearLayout, android.widget.TextView

After failing to center the title of my Window (WindowTitle) using android:windowTitleStyle, as discussed in the following thread (http://groups.google.com/group/android-developers/browse_thread/thread/d39ea52ee066dfa8/fccce4564395e08b?lnk=gst&q=center+title+gravity#fccce4564395e08b), I decided to investigate further. After looking at the source code, I narrowed my search to the internal class com.android.internal.policy.impl.PhoneWindow and layout file, screen_title.xml. Due to
the attribute android:gravity being specified on the "title" TextView, it seems to override any android:gravity set on the android:windowTitleStyle:
code snippet
<TextView android:id="@android:id/title"
style="?android:attr/windowTitleStyle"
android:background="@null"
android:fadingEdge="horizontal"
android:gravity="center_vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
To get around this, I came up with the following:
code snippet
ViewGroup decorView=
(ViewGroup) activity.getWindow().getDecorView();
LinearLayout root= (LinearLayout) decorView.getChildAt(0);
FrameLayout titleContainer= (FrameLayout) root.getChildAt(0);
TextView title= (TextView) titleContainer.getChildAt(0);
title.setGravity(Gravity.CENTER);
This should be called in the Activity.onCreate(...) after setting the content view.

Note, this does not take into account future changes to the internal window layout.

Monday, January 18, 2010

Launching SMS/Messages Activity using Intent

Category: SMS
Subcategory: SMS/Messages Activity, Intent
Android Platform: 1.5, Google APIs 3
Referenced Classes: android.content.Intent

As with other Android Apps, you can launch the built-in SMS/Messages Activity using an intent, as described in the following thread (http://jtribe.blogspot.com/2008/12/sending-sms-using-android-intents.html). In order to pre-populate the recipients (or "To" text box) in this manner, set the address attribute on the calling intent:
code snippet
Intent sendIntent= new Intent(Intent.ACTION_VIEW);
sendIntent.putExtra("sms_body", "smsBody");
sendIntent.putExtra("address", "phoneNumber1;phoneNumber2;...");
sendIntent.setType("vnd.android-dir/mms-sms");
startActivity(sendIntent);
Note, multiple recipients can be passed using a semi-colon delimited list.

Wednesday, January 6, 2010

Getting Height of Status and Title Bar

Category: UI
Subcategory: Window/Layout Dimensions
Android Platform: 1.5, Google APIs 3
Referenced Classes: android.app.Activity, android.app.Dialog, android.view.View, android.view.Window
Additional Info: Vertical Orientation

In some cases, determining the height for both Status and Title Bar is necessary. Using Window.getDecorView().getWindowVisibleDisplayFrame(...), I was able to find the height for both:
code snippet
Rect rect= new Rect();
Window window= activity.getWindow();
window.getDecorView().getWindowVisibleDisplayFrame(rect);
int statusBarHeight= rect.top;
int contentViewTop=
window.findViewById(Window.ID_ANDROID_CONTENT).getTop();
int titleBarHeight= contentViewTop - statusBarHeight;
Note, that decorView must be attached to the Window (i.e. Dialog showing), for getWindowVisibleDisplayFrame(...) to return coords properly, otherwise both rect.top and rect.left will be 0. Alternatively, any attached view from within the layout should suffice. Please, see my comments.

Tuesday, January 5, 2010

Using the Date & Time Picker Dialog

Category: UI
Subcategory: Form Design
Android Platform: 1.5, Google APIs 3
Referenced Classes: android.app.DatePickerDialog, android.app.DatePickerDialog.OnDateSetListener, android.app.TimePickerDialog

I wanted a user-friendly way to specify both date and time with a minimum number of clicks. After considering suggestions I had found in the following thread (http://groups.google.com/group/android-developers/browse_thread/thread/de5f380c370e6410/a312e14b2fe8d915?lnk=gst&q=date+picker+dialog+load+time#a312e14b2fe8d915), I decided to show the DatePickerDialog and TimePickerDialog in sequence. I did this by using DatePickerDialog.OnDateSetListener:
code snippet
private DatePickerDialog datePickerDialog= ...
private TimePickerDialog timePickerDialog= ...
private Calendar currentTime= ...

public void onDateSet(DatePicker view,
int year, int monthOfYear, int dayOfMonth)
{
// dismiss DatePickerDialog, then show TimePickerDialog
this.datePickerDialog.dismiss();
this.timePickerDialog.updateTime(
this.currentTime.get(Calendar.HOUR_OF_DAY),
this.currentTime.get(Calendar.MINUTE));
this.timePickerDialog.show();
}

Sunday, January 3, 2010

Getting Relative Coordinates from MotionEvent

Category: UI
Subcategory: Window/View Coordinates (X, Y)
Android Platform: 1.5, Google APIs 3
Referenced Classes: android.app.Activity, android.view.GestureDetector, android.view.GestureDetector.SimpleOnGestureListener, android.view.MotionEvent, android.view.View, android.view.Window, com.google.android.maps.GeoPoint, com.google.android.maps.MapView
Additional Info: Vertical Orientation

I wanted to place OverlayItems where long-press MotionEvents occurred. In order to determine the position of an OverlayItem, I needed to get the relative X and Y coordinates of the corresponding MotionEvent. As mentioned in the following forum (http://stackoverflow.com/questions/1410885/how-do-i-know-if-a-motionevent-is-relative-or-absolute), I was unable to obtain the relative X and Y coordinates using the methods MotionEvent.getX() and MotionEvent.getY(), respectively. Given that my MapView filled the entire screen, I came up with the following method:
code snippet
public static float[] getRelativeCoords(Activity activity,
MotionEvent e)
{
// MapView
View contentView= activity.getWindow().
findViewById(Window.ID_ANDROID_CONTENT);
return new float[] {
e.getRawX() - contentView.getLeft(),
e.getRawY() - contentView.getTop()};
}
Using GestureDetector and GestureDetector.SimpleOnGestureListener, and calling the above getRelativeCoords(...), I was able to place an OverlayItem where every long-press MotionEvent occurred:
code snippet
final MapView mapView= ...
final Activity mapViewActivity= this;

new GestureDetector(mapViewActivity,
new SimpleOnGestureListener()
{
public void onLongPress(MotionEvent e)
{
float[] coords= getRelativeCoords(mapViewActivity, e);
GeoPoint point= mapView.getProjection().fromPixels(
(int) coords[0],
(int) coords[1]);
// create OverlayItem with point
...
...
}
});