How to deal with deprecated classes in Android to keep compatibility

You can do that (checking the API version).

You can also use reflection to call the newer classes.

I wouldn't worry about using deprecated methods as all Android versions are backwards compatible, saying that you want to watch when things are for 3.0 Honeycomb as these are a little different.

Here's an explanation of how to use reflection: (yes it's been on SO before, so maybe search for reflection)

http://www.youtube.com/watch?v=zNmohaZYvPw&feature=player_detailpage#t=2087s

I'm looking at making the project this is in available but until then here's some code:

(You could do this in a class that extends Application i.e. one time setup)

 public static Method getExternalFilesDir;

    static {
            try {
                    Class<?> partypes[] = new Class[1];
                    partypes[0] = String.class;
                    getExternalFilesDir = Context.class.getMethod("getExternalFilesDir", partypes);
            } catch (NoSuchMethodException e) {
                    Log.e(TAG, "getExternalFilesDir isn't available in this devices api");
            }
    } 

Now getExternalFilesDir() is only available on API level 8 or above, so I want to use this if they have (Froyo), but otherwise I need another method.

Now I have my test for the method I can go ahead and attempt to use it:

  if(ClassThatExtendsApplication.getExternalFilesDir != null){
            Object arglist[] = new Object[1];
            arglist[0] = null;  
            File path = (File) ClassThatExtendsApplication.getExternalFilesDir.invoke(context, arglist);
           // etc etc
  } else {
      // Not available do something else (like your deprecated methods / or load a different class / or notify they should get a newer version of Android to enhance your app ;-))
  }

Hope that helps and shortcuts a lot of googling :-)

P.S. if in the else you want to use your deprectated methods still, just add the @SuppressWarnings("deprecation") annotation above it, This will get rid of the warning and you have done it for the right reasons as you are using the latest API's when possible.


Here is an example:

import android.os.Build;

public static int getWidth(Context mContext){
    int width=0;
    WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
    Display display = wm.getDefaultDisplay();

    if(VERSION.SDK_INT > VERSION_CODES.HONEYCOMB){                   
        Point size = new Point();
        display.getSize(size);
        width = size.x;
    } 
    else{ 
        width = display.getWidth();  // deprecated, use only in Android OS<3.0.
    } 
    return width;
} 

as you can see the section of code:

  if(VERSION.SDK_INT > VERSION_CODES.HONEYCOMB){                   
            Point size = new Point();
            display.getSize(size);
            width = size.x;
        } 

is only available for Android 3.0 and later versions, if you want this code available at least for Jelly Bean (Android 4.1) use:

  if(VERSION.SDK_INT > VERSION_CODES.JELLY_BEAN){                   
            Point size = new Point();
            display.getSize(size);
            width = size.x;
        } 

VERSION.SDK_INT The user-visible SDK version of the framework; its possible values are defined in Build.VERSION_CODES.

More info about: Build.VERSION

And you can see the VERSION_CODES constats here: Build.VERSION_CODES


first, @Graham Borland is right. You can either choose to use the old API, this completely solves the problem. Nvertheless, your software will not evolve and follow API's improvements and ultimately, will match a version of android that is not supported anymore.

The design pattern I am going to propose is based on introspection, but provides a better programming interface than the solution proposed by @Blundell. I think it's powerfull enough to inspire a standard approach for this common problem. It's based on many posts from Stack Over Flow and other forums.

First, you need to define an interface for the service you want to implement. You will be able to implement different versions of this service using different versions of the API you are interested in.

Indeed, as we are going to share some code here for loading our different implementations, we choose to use an abstract class. It will define public method signatures as an interface bu will also offer a static method to load your different implementations.

/**
 * Interface used to interact with the actual instance of MessageManager.
 * This inteface allows will be the type of the reference that will point 
 * to the actual MessageMessenger, which will be loaded dynamically.
 * @author steff
 *
 */
public abstract class MessageManager {

    /** Request code used to identify mail messages.*/
    public final static int FOR_MAIL = 0x3689;
    /** Request code used to identify SMS messages.*/
    public final static int FOR_SMS = 0x3698;

    /**
     * Start an activity inside the given context. It will allow to pickup a contact
     * and will be given an intent code to get contact pick up.
     * *@param the request code. Has to be a constant : FOR_MAIL or FOR_SMS
     */
    public abstract void pickupContact(int code);//met

    /**
     * Start an activity inside the given context. It will allow to pickup a contact
     * and will be given an intent code to get contact pick up.
     * *@param the request code. Has to be a constant : FOR_MAIL or FOR_SMS
     */ 
    public abstract void sendMessage(int code, Intent data, final String body);//met

    /**
     * Static methode used as in factory design pattern to create an instance 
     * of messageManager. Here it is combined with the singleton pattern to
     * get an instance of an inherited class that is supported by current android SDK.
     * This singleton will be created bu reflexion. 
     * @param activity the activity that needs messaging capabilities.
     * @return an instance of an inherited class that is supported by current android SDK or null, if not found.
     */
    public static MessageManager getInstance( Activity activity )
    {
        MessageManager instance = null;
        try {
            Class<? extends MessageManager> messageManagerClass = (Class<? extends MessageManager>) activity.getClassLoader().loadClass( "ca.qc.webalterpraxis.cinedroid.message.MessageManagerSDK7" );     
            Method singletonMethod = messageManagerClass.getMethod("getInstance", Activity.class );
            instance = (MessageManager) singletonMethod.invoke( null , activity);
        } catch (Throwable e) {
            Log.e( "CinemadroidMain", "Impossible to get an instance of class MessageManagerSDK7",e );
        }//met  
        return instance;
    }//met
}//interface

Then, you can provide different implementations of this abstract class using different versions of android SDK.

What is somewhat unusual with this method is that it's a factory design pattern combined with a singleton design pattern. All sub classes are requested to be singleton and to provide a static getInstanceMethod. The factory method of this abstract class will try to load a class implementing this interface. If it fails, you can downgrade your requirements to classes implementing the service and based an older APIS.

Here is an example of a class to send mails and sms using this interface. It's designed for android sdk 7.

public class MessageManagerSDK7 extends MessageManager {

    /** Used for logcat. */
    private static final String LOG_TAG = "MessageManagerSDK7";

    /** Singleton instance. */
    private static MessageManagerSDK7 instance = null;

    /** Activity that will call messaging actions. */
    private Activity context;

    /** Private constructor for singleton. */
    private MessageManagerSDK7( Activity context )
    {
        if( instance != null )
            throw new RuntimeException( "Should not be called twice. Singleton class.");

        this.context = context;
    }//cons

    /**
     * Static method that will be called by reflexion;
     * @param context the activity that will enclose the call for messaging.
     * @return an instance of this class (if class loader allows it).
     */
    public static MessageManagerSDK7 getInstance( Activity context )
    {
        if( instance == null )
            instance = new MessageManagerSDK7( context );

        instance.context = context;

        return instance;
    }//met

    /* (non-Javadoc)
     * @see ca.qc.webalterpraxis.cinedroid.model.MessageManager#pickupContact(int)
     */
    @Override
    public void pickupContact( int code )
    {
        if( code != FOR_MAIL && code != FOR_SMS )
            throw new RuntimeException( "Wrong request code, has to be either FOR_MAIL or FOR_SMS.");

        Intent intentContact = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI); 
        context.startActivityForResult(intentContact, code );
    }//met

    /* (non-Javadoc)
     * @see ca.qc.webalterpraxis.cinedroid.model.MessageManager#sendMessage(int, android.content.Intent, java.lang.String)
     */
    @Override
    public void sendMessage( int code, Intent data, final String body )
    {
        //System.out.println( "SendMessage");
        if( code != FOR_MAIL && code != FOR_SMS )
            throw new RuntimeException( "Wrong request code, has to be either FOR_MAIL or FOR_SMS.");

        int icon = 0;
        int noItemMessage = 0;
        int title = 0;

        //set the right icon and message for the dialog
        if( code == FOR_MAIL )
        {
            icon=R.drawable.mail;
            noItemMessage = R.string.no_email_found;
            title = R.string.mail_error;
        }//if
        else if( code == FOR_SMS )
        {
            icon=R.drawable.sms;
            noItemMessage = R.string.no_number_found;
            title = R.string.sms_error;
        }//if


        //compose email or sms

        //pick contact email address
        final String[] emailsOrPhoneNumbers = (code == FOR_MAIL ) ? getContactsEmails( data ) : getContactPhoneNumber( data );         

        if( emailsOrPhoneNumbers == null )
        {
            new AlertDialog.Builder( context ).setIcon( icon ).setTitle(title).setMessage( noItemMessage ).show();
            return;
        }//if

        //in case there are several addresses, we handle this using a dialog.
        //modal dialog would be usefull but it's bad UI practice
        //so we use an alert dialog, async .. 
        //all this is poorly coded but not very interesting, not worth having a dedicated inner class
        if( emailsOrPhoneNumbers.length > 1 )
        {
            selectMultipleAndSend( emailsOrPhoneNumbers, body, code);
            return;
        }//if

        if( code == FOR_MAIL )
            sendMail( emailsOrPhoneNumbers, body );
        else
            sendSMS( emailsOrPhoneNumbers, body );

    }//met

    private void sendMail( String[] emails, String body )
    {
        if( body == null )
        {
            new AlertDialog.Builder( context ).setIcon( R.drawable.mail ).setTitle(R.string.mail_error).setMessage( R.string.impossible_compose_message ).show();
            return;
        }//if
        //prepare email data

        try {
            Intent i = new Intent(Intent.ACTION_SEND);  
            i.setType("message/rfc822") ; 
            i.putExtra(Intent.EXTRA_EMAIL, emails );
            //i.putExtra(Intent.EXTRA_EMAIL, emails);
            i.putExtra(Intent.EXTRA_SUBJECT, context.getString( R.string.showtimes ) );  
            i.putExtra(Intent.EXTRA_TEXT,body);  
            context.startActivity(Intent.createChooser(i, context.getString( R.string.select_application ) ) );
        } catch (Throwable e) {
            new AlertDialog.Builder( context ).setIcon( R.drawable.mail ).setTitle(R.string.mail_error).setMessage( R.string.no_application_mail ).show();
            Log.e( LOG_TAG, "No application found", e);
        }//catch
    }//met

    private void sendSMS( String[] phoneNumbers, String body )
    {
        try {
            Intent sendIntent= new Intent(Intent.ACTION_VIEW);

            if( body == null )
            {
                new AlertDialog.Builder( context ).setIcon( R.drawable.sms ).setTitle(R.string.sms_error).setMessage( R.string.impossible_compose_message ).show();
                return;
            }//if
            sendIntent.putExtra("sms_body", body);

            String phones = "";
            for( String phoneNumber : phoneNumbers )
                phones += ((phones.length() == 0) ? "" : ";") + phoneNumber;

            sendIntent.putExtra("address", phones );
            sendIntent.setType("vnd.android-dir/mms-sms");
            context.startActivity(sendIntent);
        } catch (Throwable e) {
            new AlertDialog.Builder( context ).setIcon( R.drawable.sms ).setTitle(R.string.sms_error).setMessage( R.string.no_application_sms ).show();
            Log.e( LOG_TAG, "No application found", e);
        }//catch
    }//met

    /**
     * @param intent the intent returned by the pick contact activity
     * @return the emails of selected people, separated by a comma or null if no emails has been found;
     */
    protected String[] getContactsEmails(Intent intent)
    {
        List<String> resultList = new ArrayList<String>();
        //http://stackoverflow.com/questions/866769/how-to-call-android-contacts-list   
        Cursor cursor =  context.managedQuery(intent.getData(), null, null, null, null);      
        while (cursor.moveToNext()) 
        {           
            String contactId = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID));

            // Find Email Addresses
            Cursor emails = context.getContentResolver().query(ContactsContract.CommonDataKinds.Email.CONTENT_URI,null,ContactsContract.CommonDataKinds.Email.CONTACT_ID + " = " + contactId,null, null);
            while (emails.moveToNext()) 
            {
                resultList.add( emails.getString(emails.getColumnIndex(ContactsContract.CommonDataKinds.Email.DATA)) );
            }//while
            emails.close();

        }  //while (cursor.moveToNext())        
        cursor.close();

        if( resultList.size() == 0 )
            return null;
        else 
            return resultList.toArray( new String[ resultList.size() ] );
    }//met

    /**
     * @param intent the intent returned by the pick contact activity
     * @return the phoneNumber of selected people, separated by a comma or null if no phoneNumber has been found;
     */
    protected String[] getContactPhoneNumber(Intent intent)
    {
        List<String> resultList = new ArrayList<String>();
        //http://stackoverflow.com/questions/866769/how-to-call-android-contacts-list   
        Cursor cursor =  context.managedQuery(intent.getData(), null, null, null, null);      
        while (cursor.moveToNext()) 
        {           
            String contactId = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID));

            String name = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME)); 

            String hasPhone = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER));

            if ( hasPhone.equalsIgnoreCase("1"))
                hasPhone = "true";
            else
                hasPhone = "false" ;

            if (Boolean.parseBoolean(hasPhone)) 
            {
                Cursor phones = context.getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,ContactsContract.CommonDataKinds.Phone.CONTACT_ID +" = "+ contactId,null, null);
                while (phones.moveToNext()) 
                {
                    resultList.add( phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)) );
                }
                phones.close();
            }
        }  //while (cursor.moveToNext())        
        cursor.close();

        if( resultList.size() == 0 )
            return null;
        else 
            return resultList.toArray( new String[ resultList.size() ] );
    }//met

    private void selectMultipleAndSend( final String[] emailsOrPhoneNumbers, final String body, final int code )
    {
        int icon = 0;
        int selectMessage = 0;

        //set the right icon and message for the dialog
        if( code == FOR_MAIL )
        {
            icon=R.drawable.mail;
            selectMessage = R.string.select_email;
        }//if
        else if( code == FOR_SMS )
        {
            icon=R.drawable.sms;
            selectMessage = R.string.select_phone;
        }//if

        final boolean[] selected = new boolean[ emailsOrPhoneNumbers.length ];
        Arrays.fill( selected, true );
        new AlertDialog.Builder( context ).setIcon( icon ).setTitle( selectMessage ).setMultiChoiceItems(emailsOrPhoneNumbers, selected, new OnMultiChoiceClickListener() {

            @Override
            public void onClick(DialogInterface dialog, int which, boolean isChecked) {
                selected[ which ] = isChecked;
            }
        }).setPositiveButton( R.string.OK, new DialogInterface.OnClickListener() {

            @Override
            public void onClick(DialogInterface dialog, int which) {
                int count = 0;
                for( int s=0; s< selected.length; s ++ )
                    if( selected[s] )
                        count ++;

                String[] selectedEmailsOrPhoneNumbers = new String[ count ];
                int index = 0;
                for( int s=0; s< selected.length; s ++ )
                    if( selected[s] )
                        selectedEmailsOrPhoneNumbers[ index ++ ] = emailsOrPhoneNumbers[ s ];

                if( code == FOR_MAIL )
                    sendMail( selectedEmailsOrPhoneNumbers, body );
                else if( code == FOR_SMS )
                    sendSMS( selectedEmailsOrPhoneNumbers, body );
            }
        }).setNegativeButton( R.string.cancel , null ).show();
    }//met
}//class

And you could provide other alternatives as well. Trying to load them one after the other, descending android version numbers.

To use you messenger service is pretty simple :

MessageManager messageManager = MessageManager.getInstance( this );

if it's null, then no service matched. If it is not null, then use through the interface defined by MessageManager.

This technique could be extended and even made cleaner by including the version number an implementation is based on, and building a small bus to load classes one after the other in the right order.

All feedback welcome.

Regards, Stéphane


You have correctly identified the two possible solutions: decide at runtime which API to use, or always use the old API.

If it helps, it may only be a year or so until devices with the old API form such a small proportion of the install base that you can switch entirely to the new API and not worry about losing too many users.