• No results found

Storing the app on the SD card

In document 50 Android Hacks (Page 192-198)

Avoiding fragmentation

45.2 Storing the app on the SD card

After the previous section, we got a working application that shows how many times it was opened. Now we’ll add everything needed to make it install on the SD card instead of the internal storage.

Since Android version 8, you can add an attribute to your AndroidManifest by the name of android:installLocation. To understand what this does, let’s look at the documentation (see section 45.5):

It’s an optional feature you can declare for your application with the android:installLocation manifest attribute. If you do not declare this attribute, your application will be installed on the internal storage only and it cannot be moved to the external storage.

To make it work, we’ll need to modify AndroidManifest.xml with the following lines: <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.manning.androidhacks.hack045" android:versionCode="1" android:versionName="1.0" android:installLocation="preferExternal">

B

Sets android:installLocation to preferExternal

<uses-sdk android:minSdkVersion="8"/>

C

Sets minSdkVersion to 8

We set android:installLocation to preferExternal

B

so our application gets installed on the SD card if possible. To be able to use this feature, we would need to set the minSdkVersion to 8

C

. If we leave the code like that, users won’t be able to install it on devices with an API level less than 8. To fix this, we can modify the last line with the following:

<uses-sdk android:minSdkVersion="4" android:targetSdkVersion="8" />

What we’re saying with that line is something like this: “Compile with API Level 8 JARs and use the new APIs, but let the application be installed on devices with API Level 4 onward.” Although this works, there are some caveats. Compiling against higher API

levels will make available backward-incompatible classes and methods. To give you an example of this, if you call a method that’s not available in the running version, you’ll get a java.lang.VerifyError exception.

45.3

The bottom line

Using a compatibility class like SharedPreferencesCompat is common practice among Android developers. I recommend using the oldest supported device while develop- ing to avoid this pitfall. When you find a newer API that won’t work in that device, cre- ate this type of compatibility class and choose what to do when it’s not available.

Also remember that the targetSdkVersion is an excellent way of using new Android features without leaving out users with older versions.

45.4

External links

http://android-developers.blogspot.com/2010/07/ how-to-have-your-cupcake-and-eat-it-too.html http://code.google.com/p/zippy-android/source/browse/trunk/examples/ SharedPreferencesCompat.java http://developer.android.com/reference/android/content/ SharedPreferences.Editor.html#apply() http://developer.android.com/guide/appendix/install-location.html http://developer.android.com/reference/android/accounts/AccountManager.html http://developer.android.com/training/search/backward-compat.html

Hack 46

Backward-compatible notifications

Android v1.6+

With the release of the Android version Jelly Bean, a new notification API became available. With this new API, the notifica- tions now have actions. Actions allow the user to react to a notification without need- ing to enter an application. You can see an example of this in figure 46.1. The missed call notification offers the user two possi- ble actions: call back or send a message to the caller.

If your application uses notifications, it would be a great addition to support actions to improve the user experience. How can we use this new set of APIs but still be backward compatible? In this hack, we’ll see how to

165

Backward-compatible notifications

To see how it works, we’ll create a demo application that will mock a message applica- tion. Because the application will be backward compatible, it will have two possible flows—one using the notifications actions and one without them. To visualize this, you can see the possible flows using a device with Android v2.3.7 (see figure 46.2) without the new notification API, and one with Android v4.1.2 (see figure 46.3).

Figure 46.2 Android version 2.3.7

Figure 46.3 Android version 4.1.2

You’ll notice that without the new API, the user is obliged to enter the application. With the new API, users can delete a message without entering the application and they can reply directly without needing to go through the Activity holding the message.

Let’s now discuss how to create the application. We’ll need three Activitys:  MainActivity—This will hold a button to launch the notification.  MsgActivity—The message itself with Reply and Delete buttons.

 ReplyActivity—The Activity holding the reply EditText and the Discard and Send buttons.

There’s nothing out of the ordinary in those Activitys. You can read their code in this book’s sample code.

To handle all of the notification’s clicks, we need to use PendingIntents. The big difference between the PendingIntent and the Intent classes is that the former is used for later execution. From the documentation (see section 46.2):

By giving a PendingIntent to another application, you are granting it the right to perform the operation you have specified as if the other application was yourself (with the same permissions and identity). As such, you should be careful about how you build the PendingIntent: often, for example, the base Intent you supply will have the component name explicitly set to one of your own components, to ensure it is ultimately sent there and nowhere else.

The limitation to using PendingIntents is that we can’t do something like “Run this piece of code.” We can only launch an Activity, a Service or a BroadcastReceiver.

We’ll need to cover two types of operations in the application—the ones that don’t require a UI (delete, discard, send message) and those that do (read, reply to a mes- sage). Operations that don’t require a UI would ideally require back-end logic, so we’ll create a Service called MsgService.

We’ll also create a static class called NotificationHelper that will be in charge of all the notification logic and the creation of the PendingIntents. It’s code is the following:

public class NotificationHelper {

public static void showMsgNotification(Context ctx) {

Called by MainActivity to show notification final NotificationManager mgr; mgr = (NotificationManager) ctx .getSystemService(Context.NOTIFICATION_SERVICE); NotificationCompat.Builder builder = new NotificationCompat.Builder( ctx).setSmallIcon(android.R.drawable.sym_def_app_icon)

.setTicker("New msg!").setContentTitle("This is the msg title") .setContentText("content...") .setContentIntent(getPendingIntent(ctx)); builder.addAction(android.R.drawable.ic_menu_send, ctx.getString(R.string.activity_msg_button_reply), Reply action is added getReplyPendingIntent(ctx)); builder.addAction(android.R.drawable.ic_menu_delete, ctx.getString(R.string.activity_msg_button_delete), getDeletePendingIntent(ctx));

167

Backward-compatible notifications

mgr.notify(R.id.activity_main_receive_msg, builder.build()); }

private static PendingIntent getDeletePendingIntent(Context ctx) { Intent intent = new Intent(ctx, MsgService.class);

Delete

PendingIntent will use MsgService

intent.setAction(MsgService.MSG_DELETE);

intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); return PendingIntent.getService(ctx, 0, intent, 0); }

private static PendingIntent getReplyPendingIntent(Context ctx) { Intent intent = new Intent(ctx, ReplyActivity.class);

Reply

PendingIntent will use ReplyActivity

intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); return PendingIntent.getActivity(ctx, 0, intent, 0); }

private static PendingIntent getPendingIntent(Context ctx) { Intent intent = new Intent(ctx, MsgActivity.class);

When notification is clicked, it will use MsgActivity to show message

intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); return PendingIntent.getActivity(ctx, 0, intent, 0); }

public static void dismissMsgNotification(Context ctx) { final NotificationManager mgr; Helper method to dismiss notification mgr = (NotificationManager) ctx .getSystemService(Context.NOTIFICATION_SERVICE); mgr.cancel(R.id.activity_main_receive_msg); } }

With the NotificationHelper class, we have everything we need to handle the notifi- cations. We’ll now analyze part of the MsgService code. Because MsgService extends IntentService, this is the onHandleIntent() method:

@Override

protected void onHandleIntent(Intent intent) { if ( MSG_RECEIVE.equals(intent.getAction()) ) { handleMsgReceive(); } else if ( MSG_DELETE.equals(intent.getAction()) ) { handleMsgDelete(); } else if ( MSG_REPLY.equals(intent.getAction()) ) { handleMsgReply(intent.getStringExtra(MSG_REPLY_KEY)); } }

We’ll have one method per possible action. For the sake of brevity, let’s take a look at handleMsgDelete():

private void handleMsgDelete() { Log.d(TAG, "Removing msg...");

Removes a message instead of creates a log

B

NotificationHelper.dismissMsgNotification(this);

C

Dismisses notification

}

In a complete implementation, we’d place some back-end logic to remove a message instead of creating a log

B

. After the message is deleted, we can dismiss the notifica- tion with the help of the NotificationHelper class

C

.

We learned how to create a backward-compatible notification and how to handle the different clicks using PendingIntents. How can we avoid replication of logic when the MsgActivity’s Delete button is pressed? The secret is to let the MsgService take care of everything. For example, let’s see what the Delete button click handler inside the MsgActivity does:

public void onDeleteClick(View v) {

Intent intent = new Intent(this, MsgService.class); intent.setAction(MsgService.MSG_DELETE);

startService(intent); finish();

}

As you can see, all of the logic is handled inside the Service.

46.1

The bottom line

The new notifications API is great. The possibility of performing certain actions from a notification creates new use cases, and with the help of the support library we can make sure we don’t leave behind users who run older versions.

46.2

External links

http://developer.android.com/tools/extras/support-library.html

http://developer.android.com/reference/android/app/PendingIntent.html http://developer.android.com/reference/android/app/IntentService.html

Hack 47

Creating tabs with fragments

Android v1.6+

If you’ve been developing with Android for a while, you’ve most likely used the TabActivity class. This class allows developers to create tabs inside their applications so that users can switch between Activitys by pressing the Tab button. The big issue with the TabActivity class is that its developer ran into a lot of issues while trying to customize its look, and the class was deprecated with the release of fragments.

Although the Android SDK comes with classes such as TabHost and TabWidget to handle tabs, creating your own implementation gives you more control over your application. In this hack, I’ll show you how to avoid using the TabActivity class and instead use fragments to create a tab application. We’ll create a toy applica- tion that shows a different color in each tab. You can

169

Creating tabs with fragments

In document 50 Android Hacks (Page 192-198)