Logo Icon Logo
A Crowd-sourced Cookbook on Writing Great Android® Apps
GitHub logo Twitter logo OReilly Book Cover Art

Use Reflection to Safely Access Version-dependent API

Author: Ian Darwin -- Published? false -- FormatLanguage: W

Problem:

You need to access an Android API that was added recently, but still have your app work (possibly with less functionality) on devices running older releases.

Solution:

Use the standard Java reflection API, using Class.forName("class").newInstance().

Discussion:

As one example, we'll allow code using Android 4's CalendarContacts (discussed in 3852 to function (without this class) on Honeycomb devices.

The main activity does not import CalendarContacts. In fact, for your app to load correctly on older devices, it is crucial that you have not a single compile-time reference to the newer API.

Instead, in the main app, we try to instantiate either this class or, more commonly and as done here, our actual class that uses it. The Reflection API has been a standard part of the Java environment since about 1995, is well understood and well documented, and works pretty much the same on Android as on JavaSE. Here is the code I used in the Calendar demo to load the class that depends on CalendarContacts. In that app I have two buttons, to add a Calendar Event either using an Intent or using the CalendarContacts definitions to interface with a ContentProvider. The listener for the buttons is shown here.

    @Override
    public void onClick(View v) {
    Calendar c = ..., d = ...;
    switch (v.getId()) {
        case R.id.addUsingIntentButton:
            // This way works on almost any release...
            new AddUsingIntent().addEvent(this, title, c, d);
            break;
        case R.id.addUsingContentProviderButton:
            // This way uses API that was added late to Android (4.0, ICS).
            // So access it using Reflection API.
            try {
                String packageName = getClass().getPackage().getName();
                EventAdder eventAdder = (EventAdder)Class.forName(
                    packageName + ".AddUsingContentProvider").newInstance();
                eventAdder.addEvent(this, title, c, d);
            } catch (Exception e) {
                Toast.makeText(this, "Can't load AddUsingContentProvider: " + e,
                    Toast.LENGTH_LONG).show();
            }
            break;
        default:
            throw new IllegalArgumentException("Unknown view in onClick()");
        }
    }

The key part is Class.forName(...).newInstance(). The Class class is a "class descriptor" used by the JVM and exposed to the application on request; its forName() method loads a class (whose full package name must be given) into memory dynamically. Many examples hard-code the full package name, but this fails when one copies the code and puts it in another package, so I get the package name dynamically. You may have seen Class.forName() used way back when you first learned Java in the context of loading a JDBC database driver into memory. This call returns a reference to the Class descriptor for the given class; if the class is not available this will throw an exception. The class' newInstance() method instantiates the class as by calling its no-argument constructor. If there is no such constructor, or it's not public, another exception will be thrown. Assuming that all worked, we cast it from Object to a more specific type. In the example this is from, I am adding calendar events several different ways, so I defined this CalAdder interface (again, not for all uses of reflection but specifically for avoiding class load failures at application startup on older devices, we cannot use the name CalendarContract at compile time). Once we get past the class cast, we are ready to use the new API. If any exception is thrown, we simply note that the API is not available and work around it.

This provides a very good, and fairly easy to use technique, for using new code on new devices and avoiding this code on devices that have not been upgraded to the new release. Remember to "test early and often" when using this technique; test on a variety of devices with different versions of Android to make sure your app now functions correctly on all of them!

See Also:

Chapter 25 of my Java Cookbook covers the Reflection API in more detail.

Download:

The source code for this project can be downloaded from http://projects.darwinsys.com/CalAdder-src.zip.
No records found.