Backwards Compatibility with the N Developer Preview

(note: the following is an excerpt from an upcoming update to The Busy Coder’s Guide to Android Development)

In an ideal world, we would develop apps for Android N using the standard approaches for backwards compatibility:

  • setting minSdkVersion to the lowest that we support

  • using Build.VERSION.SDK_INT and the appropriate Build.VERSION_CODES value to gracefully degrade on older devices

  • testing our app on both old and new devices

When Android N ships in final form, presumably we will be able to do all of that.

However, the N Developer Preview follows in the footsteps of prior developer previews, making it difficult to test apps properly.

Am I On N?

To gracefully degrade on pre-N devices, we need to know whether or not we are running on N. In theory, you would use something like this:

if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.N) {
  // do something N-specific
}

Alas, this does not work, as in the N Developer Preview:

  • Build.VERSION.SDK_INT is 23, the same value as for Android 6.0
  • Build.VERSION_CODES.N is 10000

Following the approach that Google used for the M Developer Preview, you can check Build.VERSION.CODENAME instead:

public static boolean iCanHazN() {
  return("N".equals(Build.VERSION.CODENAME));
}

Later on, if and when Build.VERSION.SDK_INT and Build.VERSION_CODES.N start behaving properly, you can replace your utility method implementation with one that compares the versions more traditionally.

Running an N Build on Older Environments

If you set your minSdkVersion to something below 'N', your app continues to build properly and run on N Developer Preview environments. However, if you try to run it on older environments, you will find that they will not install the app. This makes it very difficult to test whether you are handling backwards compatibility properly.

The problem is that the build tools force both minSdkVersion and targetSdkVersion to 'N' if targetSdkVersion is set to 'N'. And, if your targetSdkVersion is set lower than 'N', you cannot test behaviors that depend upon targetSdkVersion, such as the ban on the file: Uri scheme.

You could set up two copies of your project: one configured for an N build and one configured as you had it before the N Developer Preview was released.

Another approach is to reverse the build tools behavior in certain circumstances, such as for a custom build type. This avoids duplicating the projects and perhaps getting stuff out of sync.

Here is an app/build.gradle file that does this:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 'android-N'
    buildToolsVersion "24.0.0 rc1"

    defaultConfig {
        minSdkVersion 15
        targetSdkVersion 'N'
    }

    buildTypes {
        bc {
            initWith(buildTypes.debug)
            applicationIdSuffix '.bc'
        }
    }

    // based on http://stackoverflow.com/a/27372806/115145

    applicationVariants.all { variant ->
        if (variant.buildType.name=='bc') {
            variant.outputs.each { output ->
                output.processManifest.doLast {
                    def manifestOutFile = output.processManifest.manifestOutputFile
                    def xml = new XmlParser().parse(manifestOutFile)
                    def usesSdk = xml.'uses-sdk'

                    usesSdk.replaceNode {
                        'uses-sdk'('android:minSdkVersion': '15',
                                'android:targetSdkVersion': '15')
                    }

                    def fw = new FileWriter(manifestOutFile.toString())

                    new XmlNodePrinter(new PrintWriter(fw)).print(xml)
                }
            }
        }
    }
}

If you do a debug build, you will get an N-native build. If you do a bc build instead, you will get 15 for your minSdkVersion and targetSdkVersion. Your project could use other values for those properties, of course, by updating the Groovy replaceNode closure and substituting in the values that you want.

Note, though, that Android Studio 1.5.1 does not like the bc build type and refuses to run it. Installing the app via a command-line build (e.g., gradle installBc) works fine. I have not tested this on Android Studio 2.x.

There may be other workarounds to these issues, but these are the ones that I found during my initial investigations into the N Developer Preview.