Tek Eye Logo

Tek Eye

Supporting Multiple API Versions in Android

How do you handle changes in the Android Application Programming Interface (API) in code? By placing code that uses the changed APIs into a separate class, then wrapping the use of that class in a test. The test is comparing the device’s API level against the code’s target API level. The Build class provides the relevant version numbers to use in the test. However, there are other considerations in supporting multiple API versions. These factors are explored in this article.

Android Logo

Note: This article was originally written for older versions of Android and has now been archived. Whilst some content has been updated, other content is outdated. The general advice it contains still applies, however, this article is kept for historical information only.

Writing Code for Different API Levels

New versions of Android come with changes to the API:

  • Adding new classes
  • Additions to existing classes
  • Deprecated classes and methods.

Deprecated classes and methods are those that are no longer required and will be removed in future releases. All these changes are due to the Android platform continuously evolving to take advantage of new hardware, new ideas and to improve performance.

Reference Documentation API Level Filter

The Android Reference documentation has details on all API packages and all API classes for all the Android API versions. The API documentation can apply a filter based on API level to grey out the classes and parts of classes that were not available prior to a given API version.

Support different Android versions

Each part of a class has a label to denote in which API level it first appeared:

Android class API version

The Android API Differences Report

To see an overview of the changes between an Android API level and the previous API release view the Android API Differences Report. The report is viewed online at the Android Developers web site. The address is https://developer.android.com/sdk/api_diff/X/changes.html where X is the API level to examine the differences from the previous level. For example if X is 24 then the differences report shows the changes from API level 24 (Nougat) to API level 23 (Marshmallow).

Reading the Android Devices API Version

The API version used by an Android device is read from the static Build class in the android.os package. The Build.VERSION.SDK_INT returns the API level. The various API levels are defined in Build.VERSION_CODES.

For example here is some code to test for API level 9 (the first Android Gingerbread release):

if(Build.VERSION.SDK_INT==Build.VERSION_CODES.GINGERBREAD)
    ((TextView) findViewById(R.id.textView1)).setText("Gingerbread");

(Note, since API 23, Marshmallow, the VERSION_CODES have been only the first letter of the release name, e.g. Build.VERSION_CODES.M for Marshmallow, Build.VERSION_CODES.O for the first Oreo release.)

The app running this code must have the minSdkVersion attribute in the manifest (AndroidManifest.xml) set to 4 (Donut) or higher. Prior to API level 4 SDK_INT was not available, it was Build.VERSION.SDK, a string. There are a tiny number of devices around early than API level 4, so setting the minSdkVersion to 4 or later should not be an issue, however, a workaround is discussed later.

Detect Other Android API Levels

The above code is easily extended to detected other API levels. So to detect API level 8, Froyo, the code becomes:

if(Build.VERSION.SDK_INT==Build.VERSION_CODES.FROYO)
    ((TextView) findViewById(R.id.textView2)).setText("Froyo");
if(Build.VERSION.SDK_INT==Build.VERSION_CODES.GINGERBREAD)
    ((TextView) findViewById(R.id.textView2)).setText("Gingerbread");

Assume this code is compiled against the Gingerbread (API level 9) Android Software Development Kit (SDK). There are no errors reported in the Integrated Development Environment (IDE). The code to detect API level 10, the maintenance release of Gingerbread, is added:

if(Build.VERSION.SDK_INT==Build.VERSION_CODES.FROYO)
    ((TextView) findViewById(R.id.textView2)).setText("Froyo");
if(Build.VERSION.SDK_INT==Build.VERSION_CODES.GINGERBREAD)
    ((TextView) findViewById(R.id.textView2)).setText("Gingerbread");
if(Build.VERSION.SDK_INT==Build.VERSION_CODES.GINGERBREAD_MR1)
    ((TextView) findViewById(R.id.textView2)).setText("Gingerbread MR1");

The line to detect GINGERBREAD_MR1 is giving an unresolved error. There is no way of knowing what future versions of Android will be called, therefore the Android VERSION_CODES list cannot be predefined. To resolve this error the project must be changed to compile against a later version of the Android SDK (level 10 or higher). If the SDK level the project is compiled against is not changed all the code can do is detect that the API level of the device is higher than expected:

if(Build.VERSION.SDK_INT==Build.VERSION_CODES.FROYO)
    ((TextView) findViewById(R.id.textView2)).setText("Froyo");
if(Build.VERSION.SDK_INT==Build.VERSION_CODES.GINGERBREAD)
    ((TextView) findViewById(R.id.textView2)).setText("Gingerbread");
if(Build.VERSION.SDK_INT>Build.VERSION_CODES.GINGERBREAD)
    ((TextView) findViewById(R.id.textView2)).setText("Higher than Gingerbread");

The code can be compiled against API level 9 (Gingerbread) and will still work on lower level APIs. Even though older devices, running Android Froyo (API level 8) or lower do not have Build.VERSION_CODES.GINGERBREAD defined. The code does not read Build.VERSION_CODES.GINGERBREAD, instead at compile time the value is stored into the code (because it is defined as a static constant the compiler knows it will never change and can put the value directly into the code). Thus at run time the above code is the same as:

if(Build.VERSION.SDK_INT==8)
    ((TextView) findViewById(R.id.textView2)).setText("Froyo");
if(Build.VERSION.SDK_INT==9)
    ((TextView) findViewById(R.id.textView2)).setText("Gingerbread");
if(Build.VERSION.SDK_INT>9)
    ((TextView) findViewById(R.id.textView2)).setText("Higher than Gingerbread");

The same can be done manually if there is a need to detect higher API levels without compiling against higher API SDKs, though this has little practical value:

if(Build.VERSION.SDK_INT==Build.VERSION_CODES.FROYO)
    ((TextView) findViewById(R.id.textView2)).setText("Froyo");
if(Build.VERSION.SDK_INT==Build.VERSION_CODES.GINGERBREAD)
    ((TextView) findViewById(R.id.textView2)).setText("Gingerbread");
if(Build.VERSION.SDK_INT==10)
    ((TextView) findViewById(R.id.textView2)).setText("Gingerbread MR1");

Switch Statement to Detect Multiple Android API Levels

Compiling against SDK level 9 the code to detect all versions between level 4 and 9 can be done using a switch statement:

TextView tv=(TextView) findViewById(R.id.textView2);
switch(Build.VERSION.SDK_INT) {
case Build.VERSION_CODES.DONUT:
    tv.setText("Donut");
    break;
case Build.VERSION_CODES.ECLAIR:
    tv.setText("Eclair");
    break;
case Build.VERSION_CODES.ECLAIR_0_1:
    tv.setText("Eclair 2.0.1");
    break;
case Build.VERSION_CODES.ECLAIR_MR1:
    tv.setText("Eclair MR1");
    break;
case Build.VERSION_CODES.FROYO:
    tv.setText("Froyo");
    break;
case Build.VERSION_CODES.GINGERBREAD:
    tv.setText("Gingerbread");
    break;
default:
    tv.setText("Higher than Gingerbread");
};

It is easy enough to extend this for all APIs (see the post Android Versions Numbering and API Levels to see a list of all the various releases) .

No Build.Version.SDK_INT on Cupcake

It is likely than you will not need to consider Cupcake (API level 3) devices when detecting API levels (due to the extremely low number of devices in use). For an app to run on Cupcake the android:minSdkVersion attribute is set to 3. Thus an app compiled against Gingerbread and able to run on all devices from Cupcake upwards would have this uses-sdk element in the AndroidManifest.xml file:

<uses-sdk android:minSdkVersion="3" android:targetSdkVersion="9" />

However, Build.VERSION.SDK_INT is not supported in Cupcake so the following gives an error in the IDE despite being compatible with the SDK:

if(Build.VERSION.SDK_INT==Build.VERSION_CODES.CUPCAKE)
    ((TextView) findViewById(R.id.textView1)).setText("Cupcake");

The error can be ignored by adding the annotation @SuppressLint("NewApi") before the method that has that code (see Configuring lint checking in Java. The code will run without problems on all devices with an API higher than level 4 (Donut). However, it will generate a java.lang.VerifyError exception on Cupcake devices. This is because Build.VERSION.SDK_INT does not exist on those devices. In other cases when using newer API features errors other than java.lang.VerifyError can occur, for example java.lang.NoSuchMethodError.

API Level Detecting Code on Cupcake Devices

On Cupcake (API level 3) devices the string value Build.VERSION.SDK is used. The code to detect Cupcake is:

if(Integer.decode(Build.VERSION.SDK) == Build.VERSION_CODES.CUPCAKE)
    ((TextView) findViewById(R.id.textView1)).setText("Cupcake");

However, Build.VERSION.SDK is deprecated for all later API versions.

The Support Library Helps Coding for Multiple API Versions

The problem of non-existent classes or class elements, or changes to classes in later API levels, can cause problems for apps supporting older devices. Some of the new Android classes have versions available in the Support Library, allowing a subset of newer API features to be supported in older devices. The Support Library is added automatically to new Android Studio Projects. But the Support Library does not included all the newer API features and does not run on HONEYCOMB_MR2 (API level 13) and earlier.

Detecting Cupcake and Later API Levels

It has been seen that Build.VERSION.SDK_INT is called to detect API levels, however, for Cupcake devices Build.VERSION.SDK is used. So would this code work on Cupcake devices?:

public class MainActivity extends Activity {
    @SuppressLint("NewApi")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);     
        TextView tv=(TextView) findViewById(R.id.textView1);
        if(Integer.decode(Build.VERSION.SDK) <= Build.VERSION_CODES.CUPCAKE)
            tv.setText("Cupcake or lower");
        else {
            switch(Build.VERSION.SDK_INT) {
            case Build.VERSION_CODES.DONUT:
                tv.setText("Donut");
                break;
            case Build.VERSION_CODES.ECLAIR:
                tv.setText("Eclair");
                break;
            default:
                tv.setText("Higher than Eclair");
            };
        }
    }
}

No, the java.lang.VerifyError exception still occurs. Even though the test ensures that Build.VERSION.SDK_INT is not called on Cupcake devices. Java knows that it is not available on that device when it loads the class. This will occur in other situations. Calls to newer API features can prevent a class from loading even if the call is wrapped in a check for the correct API level. To get round this problem the code accessing the newer features must be moved into a separate class to take advantage of a Java feature, the class lazy loading.

Lazy Loading is the Key to Supporting Multiple API Versions

Java tries to be efficient for performance and memory usage reasons. A class is only loaded when it is first used. That means if a class is not accessed it is not loaded. This ability can be used to get round the java.lang.VerifyError exception. Move the code that causes this error into a separate class and use the class where the code was used. In the example above the Build.VERSION.SDK_INT usage is moved to a new class, here called DetectAPI():

public class DetectAPI {
    private DetectAPI(){};
    @SuppressLint("NewApi")
    static String NameOfAPI(){
        String APIName="Unknown";
        switch(Build.VERSION.SDK_INT) {
        case Build.VERSION_CODES.DONUT:
            APIName="Donut";
            break;
        case Build.VERSION_CODES.ECLAIR:
            APIName="Eclair";
            break;
        case Build.VERSION_CODES.ECLAIR_0_1:
            APIName="Eclair 2.0.1";
            break;
        case Build.VERSION_CODES.ECLAIR_MR1:
            APIName="Eclair MR1";
            break;
        case Build.VERSION_CODES.FROYO:
            APIName="Froyo";
            break;
        case Build.VERSION_CODES.GINGERBREAD:
            APIName="Gingerbread";
            break;
        default:
            APIName="Higher than Gingerbread";
        }
        return APIName;};
}

Using the new class prevents the java.lang.VerifyError on Cupcake devices:

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);     
        TextView tv=(TextView) findViewById(R.id.textView1);
        if(Integer.decode(Build.VERSION.SDK) <= Build.VERSION_CODES.CUPCAKE)
            tv.setText("Cupcake or lower");
        else
            tv.setText(DetectAPI.NameOfAPI());  
    }
}

Integer API Level for Cupcake and Earlier

In most cases apps will support Android Donut (API level 4) and can uses Build.Version.SDK_INT to test for the API level. If there is a need to support Cupcake and earlier then it is good to have a utility class that allows for a single API level test. First generate a class that only has the integer version of the API level:

import android.annotation.SuppressLint;
import android.os.Build;
public class GetIntLevel {
    private GetIntLevel(){};
    @SuppressLint("NewApi")
    public static int DeviceLevel(){
        return Build.VERSION.SDK_INT;
    }
}

This can be used in Cupcake by wrapping it in a test on the Build.VERSION.SDK string. Also by converting that string to an integer a general class can be built that returns an integer for all API levels:

import android.annotation.SuppressLint;
import android.os.Build;
public class APILevel {
    private APILevel(){};
    @SuppressWarnings("deprecation")
    @SuppressLint("NewApi")
    static int DeviceLevel(){
        int level=0;
        if(Integer.decode(Build.VERSION.SDK) <= Build.VERSION_CODES.CUPCAKE)
            level=Integer.decode(Build.VERSION.SDK);
        else
            level=GetIntLevel.DeviceLevel();
        return level;
    }
}

This is can be used to test for any device level:

if( APILevel.DeviceLevel() == Build.VERSION_CODES.CUPCAKE) {
    //Cupcake code
}
if( APILevel.DeviceLevel() == Build.VERSION_CODES.DONUT) {
    //Donut code
}
//etc.

Another Example of Supporting Multiple API Versions in Android

Here is the code to get an Android device to operate the internal vibrator for one second (the internal vibrator is used for haptic feedback):

Vibrator vbr = (Vibrator) getSystemService(VIBRATOR_SERVICE);
if(vbr!=null)
    vbr.vibrate(1000);

The app requires the VIBRATE permission () in the AndroidManifest.xml file. The test for null is for completeness. The call to getSystemService(VIBRATOR_SERVICE) always returns an object, even if the device does not have a vibrator. In Android HoneyComb (API level 11) the Vibrator object was given another method, hasVibrator(). This allows code for the vibrator to be skipped if the device does not support it:

Vibrator vbr = (Vibrator) getSystemService(VIBRATOR_SERVICE);
if(vbr!=null && vbr.hasVibrator()){
    vbr.vibrate(1000);
}

If the minSdkVersion attribute is set to any value lower than 11 then this code produces a java.lang.NoSuchMethod exception. This code can be changed to support older devices. Again the API dependent code is moved to a separate class file:

import android.annotation.SuppressLint;
import android.os.Vibrator;
public class Shaker {
    private Shaker(){};
    @SuppressLint("NewApi")
    public static boolean canShake(Vibrator vibrator) throws NullPointerException {
        boolean bReturn=false;
        if(vibrator!=null)
            bReturn=vibrator.hasVibrator();
        else
            throw new NullPointerException();   
        return bReturn;
    }
}

The API level 11 hasVibrator() call is now separated from the main program and is only accessed if the device is Honeycomb or later:

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Vibrator vbr = (Vibrator) getSystemService(VIBRATOR_SERVICE);
        if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            //Only run Vibrator if present
            if(vbr!=null && Shaker.canShake(vbr))
                vbr.vibrate(1000);
        } else if(vbr!=null) {
            //Before honeycomb, assume Vibrator present
            vbr.vibrate(1000);
        }
    }
}

The code happily runs on all devices from API level 4, even though devices with API levels 4 to 10 do not have the hasVibrator() method. To support Cupcake simply change Build.VERSION.SDK_INT to APILevel.DeviceLevel() and use the classes previously defined.

Conclusion

The Android SDK and Java makes supporting multiple API versions possible, all the way back to the first commercially successful version of Android, Cupcake. With the Java lazy loading of classes an app can add new features if required and still work on older devices. This article lays down the framework to achieve that. For further information it is suggested that the Support Library developer documentation is read. Unfortunately Google no longer provides the Support Library Samples in The Android SDK.

See Also

Author:  Published:  Archived:

ShareSubmit to TwitterSubmit to FacebookSubmit to Google+Submit to LinkedInSubmit to redditPrint Page

markdown CMS Small Logo Icon ↓markdown↓ CMS is fast and simple. Build websites quickly and publish easily. For beginner to expert.



Articles on:

Android, HTML, VPS, Computing, IT, Computer History, ↓markdown↓ CMS



Free Android Projects and Samples:

Android Examples, Android List Examples, Android UI Examples



Maintain your PC with Piriform:

CCleaner Professional
CCleaner for Mac
Recuva Professional
Defraggler Professional
Speccy Professional

Shop at Tek Eye for PC utilities