Monday, April 30, 2012
Prevent autolaunch of soft keyboard
Android will want to give focus to an edittext when an activity loads which will automatically popup the soft keyboard. You may not want the keyboard to display until the user taps an edittext field. To prevent Android from launching the keyboard, simply request focus to some other (non-editable) field like a textview like in the code below.
TextView textViewTV = (TextView) findViewById(R.id.mytextview);
textViewTV.setFocusable(true);
textViewTV.setFocusableInTouchMode(true);
textViewTV.requestFocus();
Friday, April 27, 2012
Limiting Debug Trace to your App
I tend to debug on a device rather than an emulater and depending on which device I'm using., there might be tons of logging which basically makes trace statements unusable. Luckily in Eclipse, it's easy to create a log filter to get rid of the messages I don't want to see.
In debug mode, you'll see the logcat trace down at the bottom and probably a saved filter list to the left of the logging. Click the green cross icon and a dialog will popup with a few edit fields. The two that I use the most are Filter name (which gives your saved filter a name :-) ) and by Application Name which will limit trace to whatever application you want. The application name is basically the package name in your android manifest
manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.appulearn.coolapp.android"
So I would paste in com.appulearn.coolapp.android
You can also filter out by TAG, and depending on what I have running (like for example a bunch of services) I might create a tag filter which would match the tag name you use in your log.d(TAG, "whoops! error"); statement.
In debug mode, you'll see the logcat trace down at the bottom and probably a saved filter list to the left of the logging. Click the green cross icon and a dialog will popup with a few edit fields. The two that I use the most are Filter name (which gives your saved filter a name :-) ) and by Application Name which will limit trace to whatever application you want. The application name is basically the package name in your android manifest
manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.appulearn.coolapp.android"
So I would paste in com.appulearn.coolapp.android
You can also filter out by TAG, and depending on what I have running (like for example a bunch of services) I might create a tag filter which would match the tag name you use in your log.d(TAG, "whoops! error"); statement.
Tuesday, April 24, 2012
Message and Handlers
Message and Handlers
Handlers are a useful too for doing a task within a class in response to a change in a variable state or to an action. A common use for a handler is to update the UI once a thread is done processing (if you choose to do it this way versus asynctask.) I often use a handler when I want an activity to do something in response to the completion of a service or to button clicks or to form validation and other things that might generate UI changes. For example, let's say that the app starts up and kicks off a service. When the service completes, it fires an Intent that the activity is listening for which triggers the handler to redirect off a splash screen either to a login screen or a main screen based on what the service returns.
Now how does the activity know what to do? By opening up the intent and getting the bundle. The example below uses a handler to receive a broadcast from a service category download. If the service has processed successfully, the icon will update itself to a success icon, otherwise the error message from the service will be displayed. The service generates an intent with a bundle, and broadcasts it. The activity listens for that broadcast and processes it (code below is basically logging the intent and builds a message out of it to pass to the handler.The trigger to update the UI is a message that I create through a utility method.
private BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { Bundle data= intent.getExtras(); mHaveCategoryError = data.getBoolean(GlobalConstants.EXTRA_ERROR_STATUS, false); mDoRedirect = data.getBoolean(GlobalConstants.EXTRA_DO_REDIRECT, true); String error= data.getString(GlobalConstants.EXTRA_ERROR_MESSAGE); HashMap<String, String> dataList = new HashMap<String, String>(); dataList.put(GlobalConstants.EXTRA_ERROR_MESSAGE, error); Message message = Utils.createMessage(1, dataList); handler.dispatchMessage(message); } }; private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { Bundle b = msg.getData(); String message = b.getString(GlobalConstants.EXTRA_ERROR_MESSAGE); String messageType = b.getString(GlobalConstants.EXTRA_MESSAGE_TYPE); if (messageType!=null && messageType.equals(GlobalConstants.SERVICE_CATEGORIES_DOWNLOAD)){ ImageView iv1 = (ImageView) findViewById(R.id.categorySpinnerView); TextView statusMessageE = (TextView) findViewById(R.id.categoryStatusMessage); Drawable d= getResources().getDrawable(R.drawable.iconsuccess); if (message!=null) { d= getResources().getDrawable(R.drawable.iconalert48); statusMessageE.setText(message); } else { statusMessageE.setText(getResources().getString(R.string.categoriesDownloaded)); } iv1.clearAnimation(); iv1.setImageDrawable(d); } } } public static Message createMessage(int messageId, HashMap<String, String> dataList) { Message msg = new Message(); msg.what=messageId; Bundle data = new Bundle(); if (dataList == null) { dataList= new HashMap<String, String>(); } Set<Entry<String, String>> entries = dataList.entrySet(); Iterator<Entry<String, String>> it = entries.iterator(); while (it.hasNext()) { Entry<String, String> entry = (Entry<String, String>) it.next(); data.putString(entry.getKey(), entry.getValue()); } msg.setData(data); return msg; }
Sunday, April 22, 2012
Showing Progress in a Long Running Process
One of the great things about Android is that there often is several different ways you can implement a specific programming task. Let's say that you want to have some sort of indication that a background process is occurring, for example downloading bootstrap data or posting data to an api or simply displaying a splash screen when the app launches for a few seconds.
Iphone apps use a little spinner icon to denote this.
You could do this as out of box progress bar and there is a DownloadManager api that looks pretty interesting and I'm going to be trying it on my next project. The disadvantage of the progress bar is that phone manufacturers can change the default image associated with it which makes your GUI look different to different users.
Now another approach would be to use an imageview and Android's animation framework to spin an icon. Code is below and it's simple. This allows you to customize the spinning icon to your heart's content. On a surf report app I wrote, I spun a surfboard. On a ski report app, I spun a snowflake. On another Find Your Friend app, I actually spun to two little man images one doing a cartwheel to the left of a "LOADING...." textview and one doing a cartwheel to the right of that textview. You'll see that I'm controlling the visibility of the image view which you set and unset in your asynctask or thread handler.
<ImageView
android:id="@+id/spinnerView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="12dp" android:visibility="invisible" android:layout_centerHorizontal="true" android:layout_below="@id/logo" android:src="@drawable/loadingimagewhite" /> protected void startSpinner() { ImageView iv = (ImageView) findViewById(R.id.spinnerView); iv.setVisibility(View.VISIBLE); Drawable d = getResources().getDrawable(R.drawable.loadingimagewhite); iv.setImageDrawable(d); float width = d.getMinimumWidth() / 2; float height = d.getMinimumHeight() / 2; RotateAnimation a = new RotateAnimation(0, 360, width, height); a.setDuration(3000); a.setRepeatCount(RotateAnimation.INFINITE); a.setInterpolator(new LinearInterpolator()); a.setRepeatMode(RotateAnimation.RESTART); iv.startAnimation(a); } protected void stopSpinner() { ImageView iv = (ImageView) findViewById(R.id.spinnerView); iv.setVisibility(View.INVISIBLE); iv.clearAnimation(); } Friday, April 20, 2012
Sending an Email
The following bit of code enables your app to send an email. If a user has multiple email processing apps, like an email app and Gmail, a picker will automatically be displayed for them to select the email app they want to use to send the message.
String mMessage="Here is a very important email message that you need to read right away."; String mSubject="Urgent: Read me now!"; String mEmailTo="mom@you.com"; String mCCTo="dad@you.com"; Intent emailI = new Intent(Intent.ACTION_SEND); emailI.setType("text/plain"); if (mSubjectStr!=null) { emailI.putExtra(Intent.EXTRA_SUBJECT, mSubject); } else { emailI.putExtra(Intent.EXTRA_SUBJECT, getResources().getString(R.string.emailSubjectVal)); } if (mCCValStr!=null) { emailI.putExtra(android.content.Intent.EXTRA_CC,new String[]{ mCCTo}); } emailI.putExtra(android.content.Intent.EXTRA_EMAIL, new String[] { mEmailTo }); emailI.putExtra(android.content.Intent.EXTRA_TEXT, mMessage); startActivity(emailI);
Disabling the Back Button
In a few cases, my clients have wanted to disable the hardware back button on a screen or screens and put their own "exit" icon on the screen. This prevents the user from accidentally exiting a screen,say if they are holding a tablet and their thumb slips and/or gives them the ability to throw up an exit confirmation dialog when the layout exit button is clicked. To do this for a particular activity, simply override the onBackPressed() method.
@Override
public void onBackPressed()
{
return;
}
@Override
public void onBackPressed()
{
return;
}
Tuesday, April 17, 2012
Android Source Bug Reports
A useful tool for debugging is the Android Bug report database where known issues with the Android code base are reported and tracked. This repository is particularly useful when debugging an issue that seems to be occurring on a particular brand of handset or Android build level
A great example of using the archive to debug issues revolving around a build level or handset is the GSON issues that occur with Acer devices (and HTC) which is where I ran into it.
Sunday, April 15, 2012
Text Wrapping Expandable List Adapters
Expandable Lists allow you to have a two tiered list consisting of a list of groups and their attached children. I've used this type of adapter on a couple of projects and it's quite easy. The Groups and children can have custom layouts just like a normal list adapter. This allows you to style both the groups and the children. The group adapter automatically displays an open and close arrow icons on the right side, although the graphic used varies from device to device or Android build level to build level. On a recent project, the customer had a really long group name that wrapped behind the right hand side open and close icons. This was an easy fix though via adding a right side margin to my group's title text field as in the xml below.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="75dp" android:orientation="horizontal" android:background="@drawable/gridpaper_row"> <ImageView android:id="@+id/liImage" android:tag="togglebutton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginRight="5dp" android:layout_gravity="center_vertical" android:onClick="statusGroupClickHandler" android:src="@drawable/checkmarkuncheckedimage58x54" /> <TextView android:id="@+id/liName" style="@style/MediumBlackBoldText" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_weight=".5" android:layout_marginRight="35dp" android:layout_gravity="center_vertical" android:gravity="left|center_vertical" /> </LinearLayout>
Saturday, April 14, 2012
Determining Target API
One of the first decisions I make when implementing an Android app is what platform version target level to set. This impacts who can use your app. Choose to high and users may not be able to see your app in Google Play. Choose to low and you may miss out on some important features, bug fixes and performance gains of the more recent APIs. To help my clients decide, I show them a a great chart from Google of active devices running the different versions of Android platform.
http://developer.android.com/resources/dashboard/platform-versions.html
Use this chart to decide what build level to target. Currently (as of April 2, 2012) only about 7% of all devices are running below Android 2.2. So in my book that's the base level. Almost 85% of all devices runs 2.2 or 2.3, so bottom line? Go with 2.2 unless you're targeting a specific device or have a specific need to leverage an API only available in 3.0 or 4.0.
http://developer.android.com/resources/dashboard/platform-versions.html
Use this chart to decide what build level to target. Currently (as of April 2, 2012) only about 7% of all devices are running below Android 2.2. So in my book that's the base level. Almost 85% of all devices runs 2.2 or 2.3, so bottom line? Go with 2.2 unless you're targeting a specific device or have a specific need to leverage an API only available in 3.0 or 4.0.
Friday, April 13, 2012
Testing REST APIs
When starting a project, I always ask for a list of the APIs and sample responses and requests including auth requirements, parameters and data types. Preferably there is a document that details all of the ones required but sometimes this is provided in a wiki or back of a napkin depending on how far along the backend design is.
Once I have that documentation, I typically want to test the calls to make sure that the documentation matches the actual API. There are a bunch of tools out there that can help you test the apis, and you can google them to find a good one for your environment. I like to use the Rest Console, a Chrome plugin. It allows me to do pretty much any time of call via Post, GET, Put or Delete, allows me to set custom headers and displays the server response.
Anytime that I have an issue with an API I plug values into the console to debug what's going on. It's faster and easier than debugging it via Eclipse.
Once I have that documentation, I typically want to test the calls to make sure that the documentation matches the actual API. There are a bunch of tools out there that can help you test the apis, and you can google them to find a good one for your environment. I like to use the Rest Console, a Chrome plugin. It allows me to do pretty much any time of call via Post, GET, Put or Delete, allows me to set custom headers and displays the server response.
Anytime that I have an issue with an API I plug values into the console to debug what's going on. It's faster and easier than debugging it via Eclipse.
Thursday, April 12, 2012
Limiting Edit Text to Alphanumeric
Android provides an easy way to edit text fields by modifying the layout xml and adding an android:inputType="text". This lets you easily create some basic validations like numbers, decimal, phone or emails. But there's parameter for no alphanumeric (i.e. no special characters). To do this, you need to user an input filter like below and set the fields you want to validate with that filter in code.
This imput filter
InputFilter alphaNumericFilter = new InputFilter() {
@Override
public CharSequence filter(CharSequence arg0, int arg1, int arg2, Spanned arg3, int arg4, int arg5)
{
for (int k = arg1; k < arg2; k++) {
if (!Character.isLetterOrDigit(arg0.charAt(k))) {
return "";
}
}
return null;
}
};
mFirstName.setFilters(new InputFilter[]{ alphaNumericFilter});
Now how about an email address?
InputFilter filterEmail = new InputFilter() {
@Override
public CharSequence filter(CharSequence arg0, int arg1, int arg2, Spanned arg3, int arg4, int arg5)
{
boolean valid=false;
for (int i = arg1; i < arg2; i++) {
if (Character.isLetterOrDigit(arg0.charAt(i)) ) {
valid=true;
} else if ( arg0.charAt(i)=='@') {
valid=true;
} else if (arg0.charAt(i)=='.') {
valid=true;
}
}
if (valid) {
return null;
} else {
return "";
}
}
};
Wednesday, April 11, 2012
Acra App Crash Reporting
There is a great tool called Application Crash Report for Android (ACRA) that allows Android apps to automatically and completely silently upload force close crashes to a GoogleDoc form. More information is at http://code.google.com/p/acra/wiki/BasicSetup This open source projects provides a jar that will capture a force close and post that information to a Google documents spreadsheet. This spreadsheet contains a bunch of information including Android SDK version, time of crash and most importantly the trace from the Exception. I've used this now on 5 or 6 projects and it is invaluable.
Integrating with your code takes about an hour, maybe two including setting up the Google document form. It's really that easy.
There are two important steps:
1. Create a Google document form following the information in basic setup and make sure that it's public.
2. Add two lines of code to a custom application class and put the jar into the project
@ReportsCrashes(formKey = "dEx2QVB0aUNrSzdBV1JaOS13NW1Fd0E6MQ") public class MyApplication extends Application { @Override public final void onCreate() { // The following line triggers the initialization of ACRA ACRA.init(this); super.onCreate(); } }
I want to stress that I personally beleive that Acra is for testing only. This tool shows how easy it is for apps to report very detailed information about usage and it happens all the time. I personally don't like it without user knowledge. I don't recommend to my clients that they use this in production for the sole reason that it is reporting data about users without their knowledge. The report does not contain anything very sensitive about an individual user, however I personally feel that this type of reporting/tracking without explicit acceptance of terms and conditions is not appropriate. It is easy to turn on and off though and ACRA does provide an opt-in mechanism when a crash occurs. Using the optin and/or making it clear in terms and conditions would make the use in production more acceptable.
Tuesday, April 10, 2012
Multiple onclick events in a list view
Let's say that you have an Android adapter with a few buttons on it and you want the onclick to handle different tasks. How do you handle clicks within the row? Simple. In the xml definition for that adapter row view, assign an onclick. Take a look at the imageview below.
android:onClick="statusGroupClickHandler"
Clicks are handled by statusGroupClick Handler. Code for this gets added to your listactivity in a statusGroupClickHandler().
android:onClick="statusGroupClickHandler"
Clicks are handled by statusGroupClick Handler. Code for this gets added to your listactivity in a statusGroupClickHandler().
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="75dp"
android:orientation="horizontal"
android:background="@drawable/gridpaper_row">
<ImageView
android:id="@+id/liImage"
android:tag="togglebutton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="5dp"
android:layout_gravity="center_vertical"
android:onClick="statusGroupClickHandler"
android:src="@drawable/checkmarkuncheckedimage58x54" />
<TextView
android:id="@+id/liName"
style="@style/MediumBlackBoldText"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight=".5"
android:layout_gravity="center_vertical"
android:gravity="left|center_vertical" />
</LinearLayout>
public void statusGroupClickHandler(View v)
{
Guide item = (Guide) v.getTag();
Log.d(TAG, "Item Clicked: " + item.toString());
}
Monday, April 9, 2012
Handling soft keyboard done button
A common Android task is adding data to an app via an EditText. Some guis will have an add button, but others just use the keyboard done button to commit the new item to a list or database or submit to a server. In the latter case, you need to listen for the clicking of the DONE button on the keyboard.
This is easily handled by
1. adding to the edittext xml definition the following line
android:imeOptions="actionDone"
2. Attaching a setOnEditorActionListener(new OnEditorActionListener() method to the edittext in the activity
The example below shows how to do this.
Another option for the Java code is to create a separate OnEditorListener class and use that to process the onDone. The blog article below shows how to do that. I haven't tested the code but eyeballing it looks like it'll work or at least get you merrily along the way. It's a little bit cleaner implementation and allows for code reuse if you're going to have multiple activities with similar processing needs.
http://savagelook.com/blog/android/android-quick-tip-edittext-with-done-button-that-closes-the-keyboard#comments
<EditText
android:id="@+id/addTodoListEntryET"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:hint="@string/practiceScheduleCustomAddSection"
android:singleLine="true"
android:imeOptions="actionDone" />
mAddTodoListEntryET = (EditText) findViewById(R.id.addTodoListEntryET);
mAddTodoListEntryET.setOnEditorActionListener(new OnEditorActionListener()
{
@Override
public boolean onEditorAction(TextView arg0, int arg1, KeyEvent arg2)
{
// hide the keyboard
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(arg0.getWindowToken(), 0);
// do your work here
String listItem = mAddTodoListEntryET.getText().toString();
if (listItem!=null && listItem.length()>1) {
if (CustomApplication.DEBUG) Log.d(TAG, "Item added to TodoList"+ listItem);
int nextOrder = mItems.size();
try {
if (nextOrder!=0) {
Entry last = mItems.get( nextOrder-1);
nextOrder= last.getOrdering();
}
} catch (Exception e) {
e.printStackTrace();
}
Entry guide = new Entry();
entry.setTitle(listItem);
entry.setOrdering(nextOrder+1);
entry.setCompleted(false);
mItems.add(guide);
handler.sendEmptyMessage(1);
// clear the text
mAddTodoListEntryET.setText("");
}
return false;
}
});
Saturday, April 7, 2012
Repeating an image as a background
Often you need to use backgrounds with textures or gradients for linearlayouts, relativelayouts or other common layouts. But by default, Android will stretch the graphic distorting it, when what you really want it to do is to tile or repeat the graphic. Tilemode is what controls this android:tileMode="repeat". Note: Like many features, there are some bug reports that it doesn't work consistently across every device, so be sure to test!
To get it to repeat, you create a bitmap with the graphic you want to repeat as an xml file like below.
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/practice_backgroundtab"
android:tileMode="repeat" />
Then in the linear layout you can specify the background like normal
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="@drawable/practicebackrepeat"
android:gravity="center_horizontal" >
Thursday, April 5, 2012
Preventing another instance of an activity from launching (menu bar)
Had a feature request on an Android app for a customer where they didn't want to launch a new version of an activity from the menu IF the activity was currently the top of the activity stack (currently visible screen). Easy enough by adding a flag to the intent. There are some other ways you can do this as well by modifying the manifest. Here's an interesting article on activity launch mode that explains in gory detail the different types.
The folks out here have a great article on Android activity launchmode. Be sure to check it out!
http://intridea.com/posts/android-understanding-activity-launchmode
switch (item.getItemId())
{
case GlobalConstants.MENU_ID_ITEM1:
Intent home = new Intent(this, Plan.class);
home.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(home);
break;
case GlobalConstants.MENU_ID_ITEM2:
Intent intent = new Intent(this, Practice.class);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);
break;
case GlobalConstants.MENU_ID_ITEM3:
Intent intent2 = new Intent(this, States.class);
intent2.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent2);
break;
case GlobalConstants.MENU_ID_ITEM4:
Intent intent3 = new Intent(this,Connect.class);
intent3.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent3);
break;
case GlobalConstants.MENU_ID_ITEM5:
Intent intent4 = new Intent(this,Games.class);
intent4.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent4);
break;
default:
break;
}
Wednesday, April 4, 2012
Eclipse Resource Caching
When I have a number of services that need to be kicked off, I use resources arrays to iterate through them and fire them up. This looks something like this:
Easy fix though. Simply run a clean project before doing the build!
String[] arr = getResources().getStringArray(R.array.downloadServices);
for (int i = 0; i < arr.length; i++) {
final String serviceName = GlobalConstants.SERVICE_PACKAGE_NAME+ arr[i];
Intent intent = new Intent( serviceName);
startService(intent);
}
Now here's the rub. Sometimes, but not every time, when you generate a build either in debug or production mode, it seems like the resources get cached. So if you've made a change to the arrays.xml for example, the build may not regenerate the R file and the new service won't be a part of the new build. Easy fix though. Simply run a clean project before doing the build!
Tuesday, April 3, 2012
Replicating Tab Functionality
4/5/2012- Doing some research for another client, it turns out that in Android 3.0, TabActivity was deprecated and should not be used moving forward.
The Android tab activity is generally not what my clients want. It is hard to customize and clunky. My current client wants an Android Activity with a tab-like gui. Rather than using the Android tab activityI'm replicating by doing a linear layout of image tabs at the top of the screen with on-off states. One additional bit of tab functionality that I added is how to handle the back button to avoid adding the individual activities to the stack.
Most clients want the back button to exit all the clicked tabs while the Android stack keeps adding child activities to the stack.
For example if you have a list screen State List that goes to a "tabbed details screen" consisting of three tabs,
State Details, State Contacts and State Links.
If you click all the tabs, your activity stack would be State List-->State Details--> State Contacts-->State Links. Clicking back from State Links would take you to the state Contacts screen, when you really want to go to the State List screen. This is easily fixed by adding a finish(); after each startActivity like in my loadStateNavBar method below.
protected void loadStateNavBar(int pageId, final String stateKey) {
ImageView btnStates1 = (ImageView) findViewById(R.id.btnStates1);
btnStates1.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
Intent intent4 = new Intent(getApplicationContext() ,StateDetails.class);
intent4.putExtra(GlobalConstants.EXTRA_STATE_KEY, stateKey);
startActivity(intent4);
finish();
}
});
ImageView btnStates2 = (ImageView) findViewById(R.id.btnStates2);
btnStates2.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
Intent intent4 = new Intent(getApplicationContext() ,StateContacts.class);
intent4.putExtra(GlobalConstants.EXTRA_STATE_KEY, stateKey);
startActivity(intent4);
finish();
}
});
ImageView btnStates3 = (ImageView) findViewById(R.id.btnStates3);
btnStates3.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
Intent intent4 = new Intent(getApplicationContext() ,StateLinks.class);
intent4.putExtra(GlobalConstants.EXTRA_STATE_KEY, stateKey);
startActivity(intent4);
finish();
}
});
btnStates1.setImageResource(R.drawable.statedetails_off);
btnStates2.setImageResource(R.drawable.statecontacts_off);
btnStates3.setImageResource(R.drawable.statelinks_off);
switch (pageId)
{
case 1:
btnStates1.setClickable(false);
btnStates1.setImageResource(R.drawable.statedetails_on);
break;
case 2:
btnStates2.setClickable(false);
btnStates2.setImageResource(R.drawable.statecontacts_on);
break;
case 3:
btnStates3.setClickable(false);
btnStates3.setImageResource(R.drawable.statelinks_on);
break;
default:
break;
}
}
Monday, April 2, 2012
Camera Bug
Recently I built an Android tablet app for a customer that included the ability to save pictures and upload them to a backend. The Android app was being used by their infield personnel to track their daily progress and provide them with task information. Users could update when tasks were complete and be notified of new tasks automatically. After rolling the app out to about 300 test users, we ran into a frustrating bug where the app would periodically crash when taking the picture. No rhyme or reason to the crashes. The camera functionality would work 9.5 times out of 10, but occasionaly would crash within the Android code when the camera was trying to auto focus. Eventually we tracked it down to where the Android camera code would crash if the user clicked the Photo Capture button while the camera was trying to autofocus. Once we figured that out, it was an easy fix to prevent the user from double clicking the Photo Capture button. Simply hide the button and make it non-clickable until the picture result finishes.
Streaming Media Files using Media Player Example
A recent client for an Android project asked for an Android activity that would display an image that you can click to play a sound. Starting with Android 2.2, streaming mp3 from a url became much easier and supported within the Android base code. Unfortunately, it doesn't work reliably. However, downloading the mp3 sound files and playing them locally is very reliable. So an Android background service (not shown) downloads files as needed when the app starts and stores them locally. The mp3 sound files are small and this method pretty much ensures that they can be played i.e. doesn't depend on Internet connectivity and enables offline mode. Be sure to release the media player when you're done with the Android activity.
if (mAudioURL!=null) {
try
{
String localFilePath = cacheDirectory
+ FileUtils.getFileNameForUrl( mAudioURL);
if (CustomApplication.DEBUG)
{
Log.d(TAG, "Looking for Local Image File is at: "
+ filePath);
}
if (filePath != null && !filePath.equals(cacheDirectory)
&& FileUtils.doesFileExist(filePath))
{
mMediaPlayer.setDataSource(filePath);
mMediaPlayer.prepare();
mMediaPlayer.setOnPreparedListener(new OnPreparedListener() {
public void onPrepared(MediaPlayer mp) {
mp.start();
}
});;
mMediaPlayer.setOnCompletionListener(new OnCompletionListener(){
@Override
public void onCompletion(MediaPlayer mp) {
mMediaPlayer.reset();
}
});
}
} catch (IllegalStateException e)
{
// e.printStackTrace();
}
catch (IllegalArgumentException e)
{
// e.printStackTrace();
}
catch (IOException e)
{
// e.printStackTrace();
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mMediaPlayer != null) {
mMediaPlayer.release();
mMediaPlayer = null;
}
}
Subscribe to:
Posts (Atom)