Supporting Older Versions of Android - Android Is More than Phones - Android Application Development For Dummies, 3rd Edition (2015)

Android Application Development For Dummies, 3rd Edition (2015)

Part IV. Android Is More than Phones

Chapter 17. Supporting Older Versions of Android

In This Chapter

arrow Learning about AppCompat

arrow Using the backward‐compatibility theme

arrow Using the backward‐compatibility toolbar

arrow Understanding resource directories for different Android versions

arrow Working with right‐to‐left languages on older devices

arrow Working around newer APIs on older devices

arrow Using Android lint

It’s always nice to be able to write apps for the latest and greatest version of Android. You can reduce the complexity of your app by targeting a single version. It also makes testing your app much easier because there are fewer devices you need to test every new feature on.

Unfortunately, most of us (maybe not Uncle Jimmy) live in the real world. In the real world, not everyone who wants to use your app is necessarily using the latest version of Android.

Why is this? Because the economics of device production make it profitable for manufacturers to produce a wide range of Android handsets, but not necessarily to keep upgrading those devices after they’ve been on the market for a few years. Invariably, some devices stop getting updates, and users on those phones are stuck with whatever version of Android they can get.

Although it can be useful to support older versions of Android, it’s also important to know where to draw the line. The older you go, the more difficult your job of developing and testing your app will become. Figure 17-1 shows you the distribution of Android versions across all devices in the world as of the time of this writing.

image

Figure 171 Percentage of Android devices by OS version.

To find the latest data, visit https://developer.android.com/about/dashboards.

Using the data in the figure, you can see that about 80 percent of the market is covered by Android 4.1 and later. Knowing that the 80/20 rule says that covering the last 20 percent of the market will take 80 percent of the work, it makes sense to draw the line at Android 4.1.

For more about the 80/20 rule (also known as the Pareto Principle), visit http://en.wikipedia.org/wiki/Pareto_principle.

This chapter will show you how to make the Tasks app backward compatible to Android 4.1 Jelly Bean (API 16).

Understanding AppCompat

Google provides a library, called AppCompat, that emulates many of the features of later versions of Android on earlier versions. For example, features that were introduced in Android 5.0, such as the Toolbar and Material Design, have become available to Android 4.1 users using the AppCompat library.

You use the AppCompat library in this chapter to make your app work on Android 4.1.

For more information about the AppCompat library, visit https://developer.android.com/tools/supportlibrary/features.html and http://androiddevelopers.blogspot.com/2014/10/appcompatv21materialdesignforpre.html.

Updating the build File

The first step is to update your build file to indicate that your app supports Android 4.1 (API level 16).

Open the build.gradle file in the Tasks directory and make the following changes:

android {
compileSdkVersion 21

. . .

defaultConfig {
applicationId "com.dummies.tasks"
minSdkVersion 16 →8
targetSdkVersion 21
versionCode 1
versionName "1.0"
}

. . .

dependencies {
. . .

// For backward compatibility to 16
compile "com.android.support:appcompat-v7:21.0.0" →20

. . .
}

The following explains the code:

8 You changed the minSdkVersion from 21 to 16. This means that your app can be installed on versions of Android as old as Android 4.1 Jelly Bean (rather than Android 5.0 Lollipop). You will leave the targetSdkVersion and the compileSdkVersionalone.

It’s important to keep the targetSdkVersion as close to the latest Android version as possible. Whenever a new version of Android comes out, you should increase the targetSdkVersion (and possibly the compileSdkVersion if you want to use any new features), and then build and test your app. See Chapter 3 for more information about the minSdkVersion, compileSdkVersion, and targetSdkVersion.

20 You added the AppCompat library to your project. As mentioned in the previous section, AppCompat provides most of what you need to support the features of the newest Android OS on earlier versions of Android. It won’t do everything under the sun, but it does support everything you need for the Tasks app.

Adding the Toolbar

In Chapters 9 and 10 you used the Toolbar widget to create both visible and invisible action bars on the various pages of the Tasks app. You may not have realized it then, but Toolbar was introduced in Android 5.0 and is not available on Android 4.1.

Luckily, the AppCompat library supplies its own implementation of Toolbar which works on 4.1 and later. You just need to switch over to it.

Open the following layout files:

· activity_task_edit.xml

· activity_task_list.xml

· activity_task_list_and_editor.xml

In each file, change the Toolbar view to the android.support.v7.widget.Toolbar, as in the following example:

<android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
. . .
/>

This changes your layouts to use the AppCompat version of Toolbar. The next step is to change your Java code to also use the AppCompat version of Toolbar.

Open the following Java files:

· TaskEditActivity.java

· TaskListActivity.java

· TaskListAndEditorActivity.java

In each file, make the following changes:

import android.widget.Toolbar; →1
import android.support.v7.widget.Toolbar; →2

. . .

public class . . . extends ActionBarActivity →6
{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
. . .
setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); →12
. . .
}
}

Here is a description of each of the changes above:

1 Previously, you used the Toolbar that ships as part of Android 5.0. Remove the reference to this Toolbar; you replace it on the next line.

2 Import the AppCompat version of Toolbar rather than the Android 5.0 version. This ensures that your app won’t crash on versions of Android older than 5.0.

6 The Activity class that comes with Android 5.0 knows about the Android 5.0 Toolbar, but it does not know about the AppCompat version of Toolbar. Instead of using the built‐in version of the Activity class, use the ActionBarActivity that comes withAppCompat. The ActionBarActivity class knows about the AppCompat Toolbar.

12 Similarly, the setActionBar() method you used in the old Activity class does not know about the AppCompat version of Toolbar, so change that method call to one that does.

Using the AppCompat Theme

Open the Tasks styles.xml file. You’ll notice that the Tasks app theme inherits from the Material Design theme:

<style name="AppTheme"
parent="android:Theme.Material.NoActionBar">

This is a beautiful theme, but the problem is that the Material Design theme did not exist prior to Android 5.0. You must use a different theme if you want to support Android 4.1.

Luckily, AppCompat ships with a backward‐compatibility version of the Material Design theme. This theme looks the same as the Material Design theme on Android 5.0, and looks close to the same on older devices. You need to update your theme to use the AppCompat theme.

Make the changes in bold to your styles.xml file as shown in Listing 17-1 .

Listing 171: Making the styles.xml File Backward Compatible

<style name="AppTheme"
parent="Theme.AppCompat.NoActionBar"> →2

<item name="colorPrimary">@color/primary</item> →4
<item name="colorPrimaryDark">@color/primary_dark</item> →5
<item name="colorAccent">@color/accent</item> →6

<!--<item name="android:navigationBarColor">@color/primary_
dark</item>--> →8

. . .
</style>

<style name="AppTheme.TransparentActionBar" parent="AppTheme">
<!--<item name="android:windowTranslucentStatus">true</item>--> →14
<!--<item name="android:windowTranslucentNavigation">
true</item>--> →15
. . .
</style>

<style name="TransparentActionBar" parent="Theme.AppCompat">→19
. . .
</style>

Here is what the changes do:

2 Change your AppTheme to inherit from the AppCompat Theme.AppCompat.NoActionBar rather than from Android 5.0’s Theme.Material.NoActionBar.

4–6 Remove the android: namespace from the various theme color definitions. These three color definitions were introduced in Android 5.0, so they did not exist in 4.1. The AppCompat theme will find them without the android: namespace.

8 Comment this line out. navigationBarColor isn’t supported on 16. You fix this in a later section.

14 Comment this line out. windowTranslucentStatus isn’t supported on 16. You fix this in a later section.

15 Comment this line out. windowTranslucentNavigation isn’t supported on 16. You fix this in a later section.

19 Replace Android 5.0’s Theme.Material with AppCompat’s equivalent Theme.AppCompat.

Testing Your App

At this point, you should be able to try running your app. However, I have bad news: Your life just got a whole lot more complicated.

To thoroughly test your app, you should create the following 12 emulators, shown in Table 17-1:

Table 171 One. MILLION. Emulators . . .

Phone

Tablet

16

Nexus 5 API 16

Nexus 7 API 16

17

Nexus 5 API 17

Nexus 7 API 17

18

Nexus 5 API 18

Nexus 7 API 18

19

Nexus 5 API 19

Nexus 7 API 19

20

Nexus 5 API 20

Nexus 7 API 20

21

Nexus 5 API 21

Nexus 7 API 21

Go ahead, I’ll wait. And while you’re doing that, maybe you want to create another 12 emulators to test out the Nexus 4 and Nexus 9? But that wouldn’t be fair to all the other Android manufacturers, so maybe you want to create a few emulators for Samsung, Motorola, HTC, LG, and other manufacturers . . .

As you can see, this can quickly get out of hand. For simplicity, it’s best to just pick a few representative extremes on the device matrix and test those thoroughly. Then you can spot‐check other devices as necessary.

For this section, let’s test with the following 4 emulators (or are you an overachiever? Go ahead and create the 12 emulators from Table 17-1):

· Nexus 5 API 16

· Nexus 5 API 21

· Nexus 7 API 16

· Nexus 7 API 21

Choose Tools⇒Android⇒AVD Manager and create the four emulators. See Chapter 3 for more information about how to create emulators.

After your emulators are available, try running the app on each, one at a time. You should see something like what’s in Figure 17-2.

image

Figure 172 Tasks app on a 4.1 phone (a), 5.0 phone (b), 4.1 tablet (c), and 5.0 tablet (d).

Poke around in the app a bit. As you can see, the app seems to work fine on Android 5.0 phones and tablets, but there are some bugs on Android 4.1 devices.

Working with RighttoLeft Languages

Looking at Figure 17-2a, it appears that the Tasks app does not lay out the text on its cards correctly for Android 4.1 devices.

The reason is that we used some features in our layouts that added support for right‐to‐left languages (such as Hebrew and Arabic) in Chapter 9. However, right‐to‐left languages were not added to Android until Android 4.2, so these layouts do not render properly on Android 4.1.

To fix this, you need to change your layouts to have the proper right‐to‐left directives, but add the older non‐right‐to‐left directives as well.

For the next set of code changes, it’s helpful to look at the next two figures. Figure 17-3 illustrates how to configure things for left‐to‐right languages.

image

Figure 173 Laying out left‐to‐right languages like English.

Because you are reading this book in English, presumably you are accustomed to read in left‐to‐right languages. For left‐to‐right languages, things that are on the left of something can be thought of as at the start of the item. Things to the right can be thought of as at the end of the item.

For right‐to‐left languages, this is reversed, as you can see in Figure 17-4.

image

Figure 174 Laying out right‐to‐left languages like Hebrew and Arabic.

In the next few code blocks, you will add toLeftOf directives to anything that is currently using toStartOf. Similarly, you will add toRightOf anywhere that toEndOf is currently being used.

Open card_task.xml and make the following additions:

<ImageView
android:id="@+id/image"
. . .
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true" →5
/>

<TextView
android:id="@+id/text1"
. . .
android:layout_toEndOf="@id/image"
android:layout_toRightOf="@id/image"/> →12

<TextView
android:id="@+id/text2"
. . .
android:layout_alignStart="@id/text1"
android:layout_alignLeft="@id/text1" →18
/>

These changes do the following:

5 The ImageView is intended to align with the left side of the parent view. The way to indicate this in a non‐directional way is to say that it is aligned with the parent start, as shown on the previous line. However, for older Android versions that don’t have a non‐directional way to specify parent start, you must add the alignParentLeft directive.

12 Similarly, this text view is intended to be laid out to the right of the image on left‐to‐right devices, and to the left of the image on right‐to‐left devices. Hence, it uses layout_toEndOf on the previous line to indicate this. For older versions of Android, you must add the layout_toRightOf, which means the same thing in ­left‐to‐right languages.

18 Adds layout_alignLeft to the TextView that is already using layout_alignStart.

Next, open fragment_task_edit.xml and make the following additions:

<TextView
android:id="@+id/task_time"
. . .
android:layout_marginEnd="3dp"
android:layout_marginRight="3dp"
android:layout_alignEnd="@id/title"
android:layout_alignRight="@id/title"/>

<TextView
android:id="@+id/task_date"
. . .
android:layout_marginEnd="10dp"
android:layout_marginRight="10dp"
android:layout_toStartOf="@id/task_time"
android:layout_toLeftOf="@id/task_time"/>

<EditText
android:id="@+id/notes"
. . .
android:layout_alignStart="@id/title"
android:layout_alignLeft="@id/title"
android:layout_marginEnd="@dimen/gutter"
android:layout_marginRight="@dimen/gutter"/>

Now when you rerun the app, the text on the list card and the edit fragment should be where you expect them to be.

Fixing the Add Task Menu

If you look at the screen shots in Figure 17-2, you may notice that the Add Task menu icon is missing.

Where did it go? The answer is in the menu_list.xml file. Open it now and look at the lines that use showAsAction.

The problem is that android:showAsAction was not available on very old versions of Android, so the AppCompat library doesn’t look for it. Instead, it looks in its own namespace.

Open menu_list.xml and add the lines in bold:

<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> →2

<item
android:id="@+id/menu_insert"
android:showAsAction="always"
app:showAsAction="always" →7
. . . />

<item
android:id="@+id/menu_settings"
android:showAsAction="never"
app:showAsAction="never" →13
. . . />

</menu>

Lines 7 and 13 add a second showAsAction directive in the app: namespace in addition to the one that’s already there in the android: namespace. But before you can use the app namespace, you have to declare it as on line 2.

The http://schemas.android.com/apk/resauto namespace (also known as the res‐auto namespace) is a special placeholder namespace that Android replaces with a namespace containing your app’s package name. You use it whenever you want custom attributes, which you can create for the custom views in your app. You also use it when you need custom attributes defined by a third‐party library, such as the showAsAction attribute which is defined in the AppCompat library.

If you rerun the app on your 4.1 and 5.0 emulators, you should see that the Add Task menu has now returned.

Fixing the Window Options

If you run the app on a 5.0 phone emulator and click an item, you will notice some differences from how you built the app in Chapter 10, shown in Figure 17-5.

image

Figure 175 The Edit Task page on a 5.0 phone ­emulator.

The differences are

· The status bar at the top is no longer translucent.

· The navigation bar at the bottom is no longer translucent.

· The Save button has been pushed down.

The problem is that Android 4.1 does not support transparent/translucent action, status, and navigation bars. You commented out these lines in Listing 17-1 , and that’s why they’re broken now. You will need to add back in this functionality, but do it in a way that won’t break Android 4.1.

The solution is to create a new style specific to API level 21 (Android 5.0) but otherwise leave the existing style alone. In order to avoid copying and pasting code everywhere, share as much of the style as possible between the existing and the 5.0 styles by using inheritance.

Open styles.xml now and make the following changes:

<!-- This theme will be overridden in newer SDKs -->
<style name="AppTheme" parent="AppTheme.Base"/> →2

<style name="AppTheme.Base" →4
parent="Theme.AppCompat.NoActionBar">
. . .
</style>

<!-- This theme will be overridden in newer SDKs -->
<style name="AppTheme.TransparentActionBar"
parent="AppTheme.TransparentActionBar.Base" /> →11

<style name="AppTheme.TransparentActionBar.Base" parent="AppTheme"> →13
. . .
</style>

Here is a description of the changes you just made:

2 Adds a new style, named AppTheme, which inherits from the AppTheme.Base. AppTheme.Base is the old style you had for the app (previously it was called AppTheme). By separating them out, you created a new “base” theme with all your app’s style ­definitions, and a new theme that inherits from that base. Practically speaking, this code will still behave exactly the same as the old code. But by doing it this way, you can override values for Android 5.0 later in this section.

4 Renames the old AppTheme theme to AppTheme.Base.

11 Do the same thing here as you did on line 2. Create a new theme named AppTheme.TransparentActionBar for the edit task page, and make it inherit from AppTheme.TransparentActionBar.Base, which you create on line 13.

13 Renames the old AppTheme.TransparentActionBar to AppTheme.TransparentActionBar.Base.

If you run the app now, it still behaves exactly as it did before.

The next thing to do is to add the special 5.0‐specific overrides to a style definition that is applied only to Android 5.0. Create a new directory in the res folder named values‐v21, and then create a new file named styles.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>

<style name="AppTheme" parent="AppTheme.Base">
<item name="android:navigationBarColor">@color/primary_dark</item> →5
</style>

<style name="AppTheme.TransparentActionBar"
parent="AppTheme.TransparentActionBar.Base">
<item name="android:windowTranslucentStatus">true</item> →9
<item name="android:windowTranslucentNavigation">true</item> →10
</style>

</resources>

This file contains the style items that you commented out from Listing 17-1 . By putting them in the values‐v21 directory, you are telling Android to use resource directories to apply them only on Android 21 (also known as 5.0) or later. See Chapter 6 for more information about resource directories.

Here is more information about the previous code:

5 Sets the android:navigationBarColor in the AppTheme. This fixes the bug that caused the navigation bar to not be colored correctly on tablets. And because this change is in the values‐v21 directory, it applies only to Android 5.0 or later.

9 Tells Android 5.0 or later to set the android:windowTranslucent­Status to true, which makes the status bar translucent on the Edit page.

10 Tells Android 5.0 or later to set the android:windowTranslucentNavigation to true, which makes the navigation bar translucent on the Edit page.

Using Newer APIs

Every version of Android introduces some new APIs. For example, as you saw earlier in this chapter, Android 5.0 introduced the new Toolbar API. To use the Toolbar, the AppCompat library provides an alternative version of Toolbar that works on older versions of Android.

But what do you do if you have no equivalent for a new API in AppCompat? After all, AppCompat can’t be expected to provide ports of new functionality for every single old version of Android.

In cases where a new API isn’t available on older versions of Android, and you have no support for it in the AppCompat or other Android support libraries, you must disable that functionality in your app when it is run on versions of Android that do not support that feature.

See https://developer.android.com/tools/supportlibrary/features.html for more information about the various Android support libraries, including AppCompat.

The way to do this is to check the version of Android before you attempt to use one of these APIs. If you’re running on a version of Android that is too old, then disable that feature; otherwise, let it go through. For example, if your app uses the new Advanced Camera APIs introduced with Android 5.0, you could do something like the following:

if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ) {
String[] ids = cameraManager.getCameraIdList();
. . .
} else {
Toast.makeText(this,
"Sorry, that feature is not available on this " +
"version of Android",
Toast.LENGTH_SHORT).show();
}

You won’t need to do this for any of the APIs that the Tasks app uses, but it’s good to know what to do should you ever need it.

Using Android Lint

When dealing with backward compatibility, it’s very easy to accidentally use some APIs that are available on your current API version, but weren’t available a few years ago on older versions of Android. If you’re not paying attention and do this, everything will seem to work fine on your latest‐and‐greatest phone, but your users will see crashes on their older phones.

A great tool is available to help you find these sorts of situations before they happen. It’s called Android lint.

If you are familiar with the lint tool on other programming platforms, Android lint is very similar. Android lint examines the source code for your project and finds anything that looks suspicious and could possibly be a bug. Not all these warnings may, in fact, turn out to be bugs, but it’s important to go through each one and make sure you know whether they are or aren’t.

The reason that Android lint is so useful when working with backward compatibility is that it automatically flags any use of older APIs that haven’t been wrapped in build version checks.

To run Android lint, open a file in the Tasks project and then choose Analyze⇒Inspect Code. Click Module ’Tasks’ as in Figure 17-6 and click OK.

image

Figure 176 Choosing which modules to inspect using Android lint.

After Android lint has finished running, you see a report similar to what’s in Figure 17-7.

image

Figure 177 The Android lint report for the Tasks app.

This report gives you a list of warnings that may or may not be bugs in your app. You click each warning to view a description of it. If it’s a bug, then you should fix it. If it’s not in this particular case, you are given the option to suppress the warning. By suppressing the warning, you indicate to the lint tool that you acknowledge the warning, you have checked it, and you know it’s not an error.

Android lint can be a very powerful tool to find potential problems with your code before you release it. Make sure you run it frequently and keep your codebase clean and lint free! For more information about Android lint, visit http://d.android.com/tools/help/lint.html.