Compatibility and Android Programming

The delay in upgrades combined with regular new releases makes compatibility an important issue in Android programming. To reach a broad market, Android developers must create apps that perform well on devices running the most current version of Android plus the three to four previous versions of Android – as well as on different device form factors.

Targeting different sizes of devices is easier than you might think. Phone screens are a variety of sizes, but the Android layout system does a good job at adapting. If you need to provide custom resources or layouts based on screen size, you can use configuration qualifiers to do the job (as you will see in Chapter 17). However, for Android TV and Wear OS by Google devices (both of which also run Android), the differences in UI are large enough that you need to rethink the user interaction patterns and design of your app.

A sane minimum

The oldest version of Android that the exercises in this book support is API level 21 (Lollipop). There are references to legacy versions of Android, but the focus is on what we consider to be modern versions (API level 21+). With the distribution of Gingerbread, Ice Cream Sandwich, Jelly Bean, and KitKat dropping month by month, the amount of work required to support those older versions eclipses the value they can provide.

Incremental releases cause little problem with backward compatibility. Major versions can be a different story. The work required to support 5.x devices is not terribly significant. If you also need to support 4.x devices, you will have to spend time working through the differences in those versions. Luckily, Google has provided libraries to ease the pain. You will learn about these libraries in later chapters.

When you created the GeoQuiz project, you set a minimum SDK version within the New Project wizard, as shown in Figure 7.1. (Note that Android uses the terms SDK version and API level interchangeably.)

Figure 7.1  Remember me?

Remember me?

In addition to the minimum supported version, you can also set the target version and the compile version. Let’s explain the default choices and see how to change them.

All these properties are set in the build.gradle file in your app module. The compile version lives exclusively in this file. The minimum SDK version and target SDK version are set in the build.gradle file, but they are used to overwrite or set values in your AndroidManifest.xml file.

Open the build.gradle file in your app module. Notice the values for compileSdkVersion, minSdkVersion, and targetSdkVersion:

    compileSdkVersion 28
    defaultConfig {
        applicationId "com.bignerdranch.android.geoquiz"
        minSdkVersion 21
        targetSdkVersion 28

Minimum SDK version

The minSdkVersion value is a hard floor below which the OS should refuse to install the app.

By setting this version to API level 21 (Lollipop), you give Android permission to install GeoQuiz on devices running Lollipop or higher. Android will refuse to install GeoQuiz on a device running anything lower than Lollipop.

Looking again at Table 7.1, you can see why API level 21 is a good choice for a minimum SDK version: It allows your app to be installed on nearly 90 percent of devices in use.

Target SDK version

The targetSdkVersion value tells Android which API level your app is designed to run on. Most often this will be the latest Android release.

When would you lower the target SDK? New SDK releases can change how your app appears on a device or even how the OS behaves behind the scenes. If you have already designed an app, you should confirm that it works as expected on new releases. Check the documentation at developer.android.com/​reference/​android/​os/​Build.VERSION_CODES.html to find potential problems. Then you can modify your app to work with the new behavior or lower the target SDK.

Not increasing the target SDK when a new version of Android is released ensures that your app will still run with the appearance and behavior of the targeted version on which it worked well. This option exists for compatibility with newer versions of Android, as changes in subsequent releases are ignored until the targetSdkVersion is increased.

One important thing to note is that Google has restrictions on how low you can make your target SDK if you want to ship your app on the Play Store. As of this writing, any new apps or app updates must have a target SDK of at least API level 26 (Oreo) – or it will be rejected by the Play Store. This ensures that users can benefit from the performance and security improvements in recent versions of Android. These version requirements will increase over time, as new versions of Android are released, so make sure you keep an eye on the documentation to know when you need to update your target version.

Compile SDK version

The last SDK setting is the compileSdkVersion. This setting is not used to update the AndroidManifest.xml file. Whereas the minimum and target SDK versions are placed in the manifest when you build your app to advertise those values to the OS, the compile SDK version is private information between you and the compiler.

Android’s features are exposed through the classes and functions in the SDK. The compile SDK version specifies which version to use when building your code. When Android Studio is looking to find the classes and functions you refer to in your imports, the compile SDK version determines which SDK version it checks against.

The best choice for a compile SDK version is the latest API level available. However, you can change the compile SDK version of an existing application if you need to. For instance, you might want to update the compile SDK version when a new version of Android is released so that you can use the new functions and classes it introduces.

You can modify the minimum SDK version, target SDK version, and compile SDK version in your build.gradle file. Remember that you must sync your project with the Gradle changes before they will be reflected.

Adding code from later APIs safely

The difference between GeoQuiz’s minimum SDK version and compile SDK version leaves you with a compatibility gap to manage. For example, what happens if you call code from an SDK version that is later than the minimum SDK of Lollipop (API level 21)? When your app is installed and run on a Lollipop device, it will crash.

This used to be a testing nightmare. However, thanks to improvements in Android Lint, potential problems caused by calling newer code on older devices can be caught at compile time. If you use code from a higher version than your minimum SDK, Android Lint will report build errors.

Right now, all of GeoQuiz’s simple code was introduced in API level 21 or earlier. Let’s add some code from after API level 21 (Lollipop) and see what happens.

Open MainActivity.kt. In the OnClickListener for the CHEAT! button, add the following code to use a fancy reveal animation when CheatActivity comes on to the screen.

Listing 7.1  Adding activity animation code (MainActivity.kt)

class MainActivity : AppCompatActivity() {
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        cheatButton.setOnClickListener { view ->
            // Start CheatActivity
            val answerIsTrue = quizViewModel.currentQuestionAnswer
            val intent = CheatActivity.newIntent(this@MainActivity, answerIsTrue)
            val options = ActivityOptions
                    .makeClipRevealAnimation(view, 0, 0, view.width, view.height)

            startActivityForResult(intent, REQUEST_CODE_CHEAT, options.toBundle())
        }

        updateQuestion()
    }
    ...
}

You use the ActivityOptions class to customize how you want your activity to be started. In the code above, you call makeClipRevealAnimation(…) to specify that the CheatActivity should use a reveal animation. The values you pass into makeClipRevealAnimation(…) specify the view object to use as the source of the animation (the CHEAT! button, in this case), the x and y position (relative to the source view) to start displaying the new activity, and the initial width and height of the new activity.

Note that you named the lambda argument view, rather than using the default name it. In the context of setting a click listener, the argument to the lambda represents the view that was clicked. Naming the argument is not required, but it can help improve the readability of your code. We recommend you name the lambda argument when the body of the lambda uses the argument and it is not immediately clear to a new reader of the code what the argument represents.

Finally, you called options.toBundle() to package the ActivityOptions up into a Bundle object and then passed them to startActivityForResult(…). The ActivityManager uses the options bundle to determine how to bring your activity into view.

Notice that a Lint error appears on the line where you call ActivityOptions.makeClipRevealAnimation(…), in the form of a red squiggly under the function name and, when you click on the function, a red lightbulb icon. The makeClipRevealAnimation(…) function was added to the Android SDK in API level 23, so this code would crash on a device running API 22 or lower.

Because your compile SDK version is API level 28, the compiler itself has no problem with this code. Android Lint, on the other hand, knows about your minimum SDK version, and so it complains.

The error message reads something like Call requires API level 23 (Current min is 21). You can still run the code with this warning (try it and see), but Lint knows it is not safe.

How do you get rid of this error? One option is to raise the minimum SDK version to 23. However, raising the minimum SDK version is not really dealing with this compatibility problem as much as ducking it. If your app cannot be installed on API level 23 and older devices, then you no longer have a compatibility problem – but you also no longer have those users.

A better option is to wrap the higher API code in a conditional statement that checks the device’s version of Android.

Listing 7.2  Checking the device’s Android version first (MainActivity.kt)

class MainActivity : AppCompatActivity() {
    ...
    @SuppressLint("RestrictedApi")
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        cheatButton.setOnClickListener { view ->
            ...
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                val options = ActivityOptions
                        .makeClipRevealAnimation(view, 0, 0, view.width, view.height)

                startActivityForResult(intent, REQUEST_CODE_CHEAT, options.toBundle())
            } else {
                startActivityForResult(intent, REQUEST_CODE_CHEAT)
            }
        }

        updateQuestion()
    }

The Build.VERSION.SDK_INT constant is the device’s version of Android. You compare that version with the constant that stands for the M (Marshmallow) release. (Version codes are listed at developer.android.com/​reference/​android/​os/​Build.VERSION_CODES.html.)

Now your reveal code will only be called when the app is running on a device with API level 23 or higher. You have made your code safe for API level 21, and Android Lint should now be content.

Run GeoQuiz on a Marshmallow or higher device, cheat on a question, and check out your new animation.

The transition animations are pretty quick, and you may not be able to tell the difference between your new one and the original. To make seeing the difference easier, you can tweak your device to slow down the transitions. Open the Settings app and dive into the developer options (SystemAdvancedDeveloper options). Find Transition animation scale (under Drawing), select it, and set the value to Animation Scale 10x (Figure 7.2).

Figure 7.2  Slowing down transitions

Slowing down transitions

This will slow down your transitions by a factor of 10, which should make it apparent how different the new transition is. Return to GeoQuiz and rerun the app to see your new transition in slow mo. Undo your changes to MainActivity if you want to see what the original transition looks like as well. Before moving on, return to the developer options and return the transition animation scale back to 1x.

You can also run GeoQuiz on a Lollipop device (virtual or otherwise). It will not have the same reveal animation, but you can confirm that the app still runs safely.

You will see another example of guarding newer APIs in Chapter 27.

Jetpack libraries

While guarding API levels works, it is not optimal for a couple reasons. First, it is more work for you as a developer to support different paths in your app for different versions. Second, it means that users of your app will have a different experience based on the version their device is running.

In Chapter 4, you learned about the Jetpack libraries and AndroidX. In addition to offering new features (like ViewModel), the Jetpack libraries offer backward compatibility for new features on older devices and provide (or attempt to provide) consistent behavior when possible across Android versions. At the very least, using the libraries allows you to write as few conditional API checks as possible.

Many of the AndroidX libraries in Jetpack are modifications of previous support libraries. You should strive to use these libraries any time you can. This makes your life as a developer easier, because you no longer have to guard against different API versions and handle each case separately. Your users also benefit, because they will have the same experience no matter what version their device is running.

Unfortunately, the Jetpack libraries are not a compatibility cure-all, because not all the features you will want to use are available in them. The Android team does a good job of adding new APIs to the Jetpack libraries as time goes on, but you will still find cases where a certain API is unavailable. In this case, you will need to use explicit version checks until a Jetpack version of the feature is added.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.178.133