Sunday, February 24, 2013

Google Maps API v2 on Android

The Splash Screen
I don't even remember when was the last time I did something with MapView on Android. I was aware of the new version 2 of the Google Maps API, but never got a chance to work on it until today.

There were initial hitches to setup the project. But, once the first steps were done, it was quite easy to build up a simple app.

I want to list down the first steps of the process, and then move on to the sample application.

Step 1: Getting the API key.

  1. Go to Google API Console
  2. Go to Services link, and enable Google Maps Android API v2. (Don't confuse this with Google Maps API v2. I got confused, and was wondering what was I doing wrong when the maps failed to load)
  3. After enabling the service, go to the API Access link.
  4. In the Simple API access section, click on "Create New Android Key".
  5. Enter your keystore's SHA1 fingerprint along with your application's package name separated by a semi-colon in the dialog and click on "Save".
  6. Your API should be visible now on the same page.
Step 2: Setting up the project.
  1. You need to find the library project for the Google Maps API v2. It should be available in your SDK directory. If not, open up the Android SDK manager and download the same by selecting Google Play Services in the Extras section.
  2. You would find the required library project in the following location of the SDK folder. (..\android-sdks\extras\google\google_play_services\libproject)
  3. My suggestion would be to make a copy of this project somewhere else, and then import the project into eclipse.
  4. Once imported into Eclipse, use this as a library project for your application.
Step 3: Setting up the manifest file.
  1. Your manifest file should look like this. AndroidManifest.xml
  2. You can change the package names for the permissions to use your own package name.
  3. Now, replace your API key with the place-holder API Key in the manifest.
Step 4: MapFragment and more code...
  1. To show a map, I used the MapFragment in the activity's layout.
That's it. These are the broad steps to configure the new version of Google Maps for your Android application.

Your Places
Add a new place

What this application does?
  1. On opening the application, you would see a splash screen, and it would try to get your current location.
  2. Once it gets your location, it would switch to the map view, and display your current location with a marker.
  3. Tapping on an unmarked area of the app, you would see a dialog, through which you can add a new place.
  4. After saving the new place by providing a name and a description, this place would be saved into the database and you would be able to see the newly added place on the map.
  5. Tapping on an already visible marker will show you the details of that place.
  6. The next time you open the application, all the previously stored places would appear on the map along with your current location.
Suggested optimizations
  1. While loading the points from the database, it would be nice if we can only load the places which are within the currently visible area of your app.
  2. When the map is re-sized or moved, we could re-query the database for the fresh list of places and update the markers based on the new visible area.
Problems:
  1. The VisibleRegion API, currently, doesn't work. Need to think of a workaround for this.
You can find the full source-code here at the github respository: android-map101-v2

Thursday, November 15, 2012

Easier Bug Reporting on 4.2

With the recent release of an updated Jelly Bean version, i.e, 4.2, there have been quite some new things to awe you.

These are two things that could probably make a developers life easier.


  • Take bug report — immediately takes a screen shot and dumps device state information to local file storage, then attaches them to a new outgoing email message.
  • Power menu bug reports — Adds a new option to the device power menu and quick settings to take a bug report (see above).
Remember, while you were testing your app on a bus, or you were away from your desktop/laptop and got a dreaded crash!! You, so eagerly want to have a look at the logcat, or even save the logcat output for later investigation. And most of the times, I don't have SendLog, which I could fire up, and send me the logs.

With 4.2, it's already built-in to your phone. Here are a few screenshots, that give you an idea.


Enable this option from the Settings Page.


Hold the power button, to see the option to capture a "Bug Report"

Happy Coding...

Thursday, August 23, 2012

Check orientation of images/captures

A lot of times, you would need your app to either pick an image from the gallery or use the device's camera for capturing a picture that your app could use. I have seen a lot of apps, do it plain wrong. Especially, the orientation of the images.

The default gallery app, reads the orientation properly, and displays the images/thumbnails properly. So, our apps can also handle images in various orientations properly. And the good news is, it's very easy to handle.

There's a class called ExifInterface. Most of the times, when you have a similar situation, you would almost never want a full-scaled image to be shown in your app. Most often, we use a thumbnail view for the purpose. The following code would get you a re-sized bitmap, from your original file.

Say for example, we have this path to the actual image file. imagePath

1. Create a Bitmap from the file

Bitmap b = BitmapFactory.decodeFile(imagePath);

2. Resize the Bitmap by scaling it to appropriate level
int width = b.getWidth();
int height = b.getHeight();
int newWidth = 150;
int newHeight = 150;
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
Matrix matrix = new Matrix();
matrix.postScale(scaleWidth, scaleHeight);
// Bitmap resizedBitmap = Bitmap.createBitmap(b, 0, 0, width, height, matrix, true);
// resizedBitmap.compress(Bitmap.CompressFormat.JPEG, 70, out);

3. Handle orientation of the image
ExifInterface exif = new ExifInterface(imagePath);
String orientation = exif.getAttribute(ExifInterface.TAG_ORIENTATION);
if (orientation.equals(ExifInterface.ORIENTATION_NORMAL)) {
        // Do nothing. The original image is fine.
} else if (orientation.equals(ExifInterface.ORIENTATION_ROTATE_90+"")) {
        matrix.postRotate(90);
} else if (orientation.equals(ExifInterface.ORIENTATION_ROTATE_180+"")) {
        matrix.postRotate(180);
} else if (orientation.equals(ExifInterface.ORIENTATION_ROTATE_270+"")) {
        matrix.postRotate(270);
}

4. Save the new bitmap 
out = new FileOutputStream(new File("some output file path"));
Bitmap resizedBitmap = Bitmap.createBitmap(b, 0, 0, width, height, matrix, true);
resizedBitmap.compress(Bitmap.CompressFormat.JPEG, 70, out);
Now your output file would be an image that is resized and handled properly for orientation of the images. You could directly use the "resized" bitmap, but I prefere files.



Thursday, August 9, 2012

Android Themes: A dialog without a title


Most of the times, I have seen developers not leveraging the power of themes and styles. Themes and styles are a great way to easily create UI that are  manageable and compatible for various platforms. Here's the official documentation that explains styles and themes in details, and I consider this portion of documentation to be equally important to any developer working with Android.

For the example of this post, we will see how to make a dialog, a custom dialog, not to have a title. The easiest way to do this, is through code byt writing this snippet in your custom dialog class.

requestWindowFeature(Window.FEATURE_NO_TITLE);

Dialog with Title on Android 2.2


Dialog with no title on Android 2.2

While this would work anyway, and would also be compatible with all the versions of Android. But, it's a good idea to use more of your themes and styles in your programming, so that your code base is maintainable and your apps would behave and feel consistently. Using styles and themes makes it very easy to tweak or adapt to various platform versions and/or device sizes and resolutions. So, let's get in.

The application tag, has a configurable attribute called "android:theme" with which you can set a theme to your application as a whole. You can also specify themes for individual themes for all your activities separately. Sounds nice!!! But, let's stick to one theme for our application as a whole for simplicity.

For this example, we have a theme (it's actually called a style), called MyThemeSelector as shown below. This is specified in the styles.xml in your values folder. Notice, your custom theme is a child of the Theme, which is one of the system's default themes.

<resources>        <style name="MyThemeSelector" parent="@android:style/Theme"></style></resources>

Ideally, you should also declare your custom theme to extend one of the basic themes that are available with platforms on or above Honeycomb. For example, here we have created another styles.xml in a folder called values-v11, which looks like this.

<resources>
        <style name="MyThemeSelector" parent="@android:style/Theme.DeviceDefault.Light"></style>
</resources>

So, your basic theme is now compatible with both older versions and versions greater than Honeycomb. By this, I mean, that, when you run the app, your app would adapt to the platform that it is running on, and would give a consistent look and feel for your users.

Now, coming back to the main problem. "Creating a dialog without title". Here also, we would use themes, as against code. Here are the two new themes that you would be declaring.

For values folder: (Platform versions older than Honeycomb)

<style name="My.Theme.Dialog" parent="@android:style/Theme.Dialog">
            <item name="android:windowNoTitle">true</item>
</style>
<style name="MyThemedDialog" parent="@style/My.Theme.Dialog"></style>

For values-v11 folder: (Platform version for Honeycomb and above)

<style name="My.Dialog.Theme" parent="@android:style/Theme.DeviceDefault.Light.Dialog.NoActionBar"></style>
<style name="MyThemedDialog" parent="@style/My.Dialog.Theme"></style>
There's a subtle difference between the two versions. Of course, other than the parent classes. In older platform versions, there wasn't a version of the Dialog theme without a title bar. So, what we do here, is to extend the basic Dialog them, and overwrite an attribute, so that our custom "My.Theme.Dialog" is a dialog without a title bar.

But, for Honeycomb and above, the platform itself provides a version of the Dialog theme, without the title bar (or the Action Bar).

And finally, the last step for getting everything to work is set the theme to your dialogs.
MyDialog dialog = new MyDialog(this, R.style.MyThemedDialog);
dialog.setTitle("Here's my title");
dialog.show(); 
Why didn't we use "My.Theme.Dialog" directly? Well, we could still try and tweak the theme for older versions, in the values folder, by adding a few more attributes.


Dialog with title on Android 4.1


Dialog with no title on Android 4.1

As you can see, for both older and newer platforms, the app runs by adapting itself and gives a consistent look and feel. You didn't have to do much with your code. 

15 lines of XML is the MAGIC here!!!!  (I didn't count though, just guessing)

Sample project here.

Friday, February 3, 2012

Sending SMS on Android and tracking it

Lets look at how to send an SMS. It's pretty simple though. A few lines of code and your SMS is gone. To make it more convenient and meaningful, we should also be able to track and let the user know when the SMS is sent and when it is delivered. For a start, I won't be looking into how to trap the error messages here and log or show it to the user. May be, when I get some more time, I will update this post. For now, lets track our SMS.
        Intent sentIntent = new Intent(INTENT_ACTION_SENT);
        PendingIntent pendingSentIntent = PendingIntent.getBroadcast(this,
                REQUEST_CODE_ACTION_SENT, sentIntent,
                PendingIntent.FLAG_UPDATE_CURRENT);

        Intent deliveryIntent = new Intent(INTENT_ACTION_DELIVERY);
        PendingIntent pendingDeliveryIntent = PendingIntent.getBroadcast(this,
                REQUEST_CODE_ACTION_DELIVERY, deliveryIntent,
                PendingIntent.FLAG_UPDATE_CURRENT);

        SmsManager smsManager = SmsManager.getDefault();

        // Second parameter is the service center number. Use null if you want
        // to use the default number
        smsManager.sendTextMessage(number, null, message, pendingSentIntent,
                pendingDeliveryIntent);

In the above code snipped, you can see that we are passing 2 pending intents to the SMSManager, one of which will be fired when the SMS is sent, and the other, when the SMS is delivered. It would also let you know the error type if the sending or delivery fails, so that you can take action for the errors. INTENT_ACTION_SENT and INTENT_ACTION_DELIVERY are string constants, which are just some random actions required to setup of the PendingIntents and receive them back.

Setting up the SMS is super easy. How do we track or listen to the updates, which happen through the PendingIntents? Well, those pending intents could be for starting an Activity, a Service or sending out a Broadcast. As you can see, here, I have used a Broadcast, to keep it simple. So, in our activity, we would need to register BroadcastReceivers for the same actions.
        IntentFilter filter = new IntentFilter(INTENT_ACTION_SENT);
        filter.addAction(INTENT_ACTION_DELIVERY);

        registerReceiver(smsSentDeliveredReceiver, filter);

Now, the onReceive() method will be fired, when those events happen, and thus you can notify the user about when the message is sent and delivered.
        String action = intent.getAction();
        Log.i(TAG, "Received: " + action);

        if (action.equals(INTENT_ACTION_SENT)) {
            Log.i(TAG, "Message: Sent");
            Toast.makeText(this, "Message sent", Toast.LENGTH_LONG).show();
        } else if (action.equals(INTENT_ACTION_DELIVERY)) {
            Log.i(TAG, "Message: Delivered");
            Toast.makeText(this, "Message delivered", Toast.LENGTH_LONG).show();
        }

You can find the sample app and the source code here. Give it a run.

Note: Using this example, you cannot send SMS to real numbers from an emulator. The SMS will be sent, but it will never be delivered.