The first flavor
As I mentioned earlier this is the flavor where the API did not exist in the older version. I will elaborate on an example with the AccountManager. The base of the solution is to isolate the behavior that causes the problem (i.e. the AccountManager stuff) in a separate component. The first step is to design an interface through which the component will communicate with the rest of the world. In this case I will use the following interface:
public interface IGoogleAuth {
List<String > getGoogleAccounts();
...
}
Next I will implement it:
class GoogleAuth implements IGoogleAuth {
private static final String ACCOUNT_TYPE = "com.google";
@Override
public List<String > getGoogleAccounts(){
AccountManager accountManager = AccountManager.get(App.getContext());
Account[] accounts = accountManager.getAccountsByType(ACCOUNT_TYPE);
ArrayList <String > accList = new ArrayList();
if (accounts != null && accounts.length > 0) {
for(Account a: accounts) {
accList.add(a.name);
}
}
return accList;
}
AccountManager accountManager = AccountManager.get(App.getContext());
Account[] accounts = accountManager.getAccountsByType(ACCOUNT_TYPE);
ArrayList
if (accounts != null && accounts.length > 0) {
for(Account a: accounts) {
}
}
return
}
...
}
}
The code is pretty straightforward. First an AccountManager instance is requested from the application and then the instance is queried for Google Accounts (they have the type "com.google"). If the account manager returns any accounts, their name (email) is added to the list that is returned to the caller. Note that the GoogleAuth class is package private since it should not be used outside the enclosing package. One important point to note is that the interface must not use any classes that are defined in the new API because then the application would throw an exception probably causing a Force Close dialog when run on a device with an older Android version. In other words, the interface must only use classes that all involved versions can process. For example, the interface must not use the Account class in the following way:
public interface IGoogleAuth {
List<Account > getGoogleAccounts();
...
}
since it would crash on an older Android version. Now that the implementation is finished we can implement the factory class:
public enum GoogleAuthFactory {
;
private static IGoogleAuth auth = null;
private static boolean isSet = false;
public static boolean hasGoogleAuth() {
int sdkVersion = Integer.parseInt(Build.VERSION.SDK);
return sdkVersion >= Build.VERSION_CODES.ECLAIR;
}
public static IGoogleAuth get() {
if (!isSet) {
if (hasGoogleAuth()) {
auth = new GoogleAuth();
}
isSet = true;
}
return auth;
}
}
All that remains is to use the code. It can be used in the following way:
if(GoogleAuthFactory.hasGoogleAuth()) {
//This device has a version that implements the api
IGoogleAuth auth = GoogleAuthFactory.get();
List<Account > accountList = auth.getGoogleAccounts();
// Display the account list
} else {
} else {
//This device has an older version. Do something else.
}
The second flavor
Now let's examine the second type of problem that includes an API that both versions support but has changed drastically between versions. This problem is actually very similar to the first one but it has its own set of tips and tricks. To elaborate this problem I will use the Contacts API that has changed a lot between versions 1.6 and 2.0. Let's say that we want to start the default activity for adding a new contact to our contacts. To start the activity we need an intent. Such an intent exists, of course, but it looks like this in version 1.6:
Intent i = new Intent(Contacts.Intents.Insert.ACTION, Contacts.People.CONTENT_URI);
and it looks like this in version 2.0:
Intent i = new Intent(Intent.ACTION_INSERT, ContactsContract.Contacts.CONTENT_URI);
It should be noted that in this example starting the activity with the version 1.6 intent on the version 2.0 device would probably work but I am using it here just to demonstrate a technique and to keep the code snippet size at bay. But with this technique You could also implement a method that returns a list of contacts that have an email address which is a lot less trivial task to implement on both versions (that's another story). The base of the solution is the same as with the first kind of problem. We need to isolate the different API's in separate components. First we will design the interface to the world. It looks like this:
public interface IContact {
Intent getAddContactIntent();
}
}
class ContactVersion4 implements IContact {
@Override
public Intent getAddContactIntent() {
return new Intent(Contacts.Intents.Insert.ACTION, Contacts.People.CONTENT_URI);
}
}
return new Intent(Contacts.Intents.Insert.ACTION, Contacts.People.CONTENT_URI);
}
}
class ContactVersion5 implements IContact {
@Override
public Intent getAddContactIntent() {
return new Intent(Intent.ACTION_INSERT, ContactsContract.Contacts.CONTENT_URI);
}
}
return new Intent(Intent.ACTION_INSERT, ContactsContract.Contacts.CONTENT_URI);
}
}
public enum ContactFactory {
;
private static IContact contact = null;
public static IContact get() {
if (contact == null) {
int sdkVersion = Integer.parseInt(Build.VERSION.SDK);
if (sdkVersion <= Build.VERSION_CODES.DONUT) {
contact = new ContactProcesorStari();
} else {
contact = new ContactProcesorNovi();
}
}
return contact;
}
}
The get method determines the Android version on the first invocation and creates the appropriate IContact implementation according to the version. The reference is then cached for future invocations. The only thing that remains to do is to use the factory. This is how it is done:
IContact contact = ContactFactory.get();
startActivityForResult(contact.getAddContactIntent(), 1);This code is safe to use on both Android version 1.6 and version 2.0. A variant of the factory implementation involves using reflection but I do not like to use reflection unless I absolutely have to. I hope this will help someone. Forgive my beginner's mistakes since this is the first blog I have written.
References
Nick's blog
Working with Android contacts
No comments:
Post a Comment