I understand there are (at least) two ways to do runtime checks to ensure my code doesn't call APIs that don't exist:
Use a conditional version number check, a la if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
Use java.lang.reflect and wrapper class techniques as explained here: http://android-developers.blogspot.com/2009/04/backward-compatibility-for-android.html
But I don't understand when one technique should be used in lieu of the other. Reflection seems necessary when trying to use Android classes that may not exist, since loading such a class referenced in my code will cause a fatal error. But what about calling a method belonging to a class that I know exists? For example, on a device running 2.2 (API 8):
protected void onCreate(Bundle savedInstanceState) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
ActionBar actionBar = getActionBar();
}
...
Is this always safe, or are there circumstances where a crash will happen? Is there any reason to check if the "getActionBar" method exists in Activity using reflection instead of using the above version check?
But I don't understand when one technique should be used in lieu of the other.
Use the first bulleted technique.
Is this always safe, or are there circumstances where a crash will happen?
First, you need to change that to HONEYCOMB, as the ActionBar class was added in API Level 11.
Beyond that, so long as you are only supporting Android 2.0 and higher, you are fine. If you are supporting Android 1.x still, you would need to rewrite your code as:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
HoneycombHelper.doSomethingCoolWithTheActionBar();
}
where HoneycombHelper is another class you write that contains your API Level 11 code (e.g., ActionBar actionBar = getActionBar()).
This difference is because Dalvik on Android 1.x would fail fast, giving you a VerifyError as soon as you try loading a class that contains an unrecognized reference. Android 2.0+ will not fail until you actually execute the statement containing the unrecognized reference, and your if test should prevent this.
Is there any reason to check if the "getActionBar" method exists in Activity using reflection instead of using the above version check?
No. Just set your build target (e.g., Project > Properties > Android in Eclipse) to a value high enough to cover everything you want to refer to, and set your android:minSdkVersion to the level you are willing to support. Anything that you try using that is newer than android:minSdkVersion will be flagged with an error by Lint, and you can add your Java version guard block (if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.WHATEVER)) and the #TargetApi() annotation to get Lint to stop complaining.
Related
I wonder if it's possible to implement multiple methods to support different API levels and call the right one without if(android.os.Build.VERSION.SDK_INT >= ...) else if...?
I want to use android platform's newer features like streams etc. and still support backwards.
Example:
wrote a method
public void myMethod24() {
// some logic requires api level N(24) and above
}
but my app supports lower apis, so i need another method that's compatible with them.
here is a method compatible for older versions:
public void myMethod21() {
// the same logic, requires api level LOLLIPOP(21) and above
}
How to use the correct method for current running version without doing this ugly if else:
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
myMethod24();
} else if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
myMethod21();
}
Maybe annotate my methods with #RequiresApi(), #TargetApi or something else..?
i saw this question but the answer there is with if else.
How to use the correct method for current running version without doing this ugly if else
You can't. Somewhere, something needs to do the version check and route to the appropriate logic.
Maybe annotate my methods with #RequiresApi(), #TargetApi or something else..?
Those are there to help advise the compiler that you know what you are doing. They do not code-generate the version comparisons.
Depending on what you are doing, existing ...Compat classes might handle the version checks for you (e.g., NotificationCompat.Builder). If you were using Kotlin, we could come up with some funky code that hides the if checks. And there might be a third-party library that offers some annotation-based code generator that code-generates the if checks.
TL;DR: Can Android's #SuppressWarnings("deprecation"), or similar, be applied to a single statement, rather than an entire method?
I have a method myMethod that uses deprecated method ImageView.setAlpha():
public void myMethod(ImageView icon) { icon.setAlpha(0xFF); }
To avoid use of the deprecated method in Jelly Bean and subsequent releases, whilst providing backward compatibility, method myMethod can be rewritten as follows:
public void myMethod(ImageView icon) {
if (android.os.Build.VERSION.SDK_INT
>= android.os.Build.VERSION_CODES.JELLY_BEAN)
icon.setImageAlpha(0xFF);
else
icon.setAlpha(0xFF);
}
Moreover, command line warnings generated by Gradle/Lint can be suppressed by prepending method myMethod with #SuppressWarnings("deprecation"). But, that suppresses all deprecated warnings generated by method myMethod, rather than just the single warning generated by statement icon.setAlpha(0xFF).
Can I suppress the single deprecation warning generated by statement icon.setAlpha(0xFF), rather than suppressing all deprecation warnings generated by method myMethod?
You can achieve it as follows if you are using Android Studio:
//noinspection deprecation
icon.setAlpha(0xFF);
For your future reference: The correct format can be easily generated in Android-Studio as follows:
Press alt+Enter on the statement which is throwing warning.
Then Expand the option Deprecated API usage options
Click on Suppress for statement
Following Image shows the process:
In your case since you are not using IDE:
Unfortunately there is no direct way to achieve it at method body level. Since you have already moved the deprecated part in individual method and marked it with #SuppressWarnings this should be best you can achieve.
There are some posts which claim to have solved it by using fully qualified class name instead of import. But looks like the issue has been fixed in Java 9. Since current popular java version for android is 8.x this should help in short term. You can refer this SO for more details
I am developing an app which should perform differently based on the different Android Versions. For instance if a user has version below 3.0, the font should be "Anmol.ttf" but if it's above 3.0 the font should rather be "AnmolUniBani.ttf" How can I approach to this solution?
There are different ways to go about this. I do
android.os.Build.VERSION.SDK_INT;
this gets the version number 10,11, 14,...
See the Documentation for the different possibilities.
I don't know if this is preferred but I set mine in a static variable on startup
public static int androidVersion = android.os.Build.VERSION.SDK_INT;
then check against that. I'm going to move it to SharedPreferences but haven't yet.
Code Example
For 3.0 and above check that the SDK_INT >= 11. So in my example
if (Globals.androidVersion >= 11)
{
// change fonts or whatever
}
It is in a Globals class for now where I put things for testing before making them more permanent as in SharedPreferences or the DB
I want to use new UI elements like Switch for new Android devices, and by older devices I would use something else like Button.
I have created to layouts
~/res/
layout/main.xml
layout-v14/main.xml
that has different elements like Button in layout/main.xml and Switch in layout-v14/main.xml
But how can I add different elements in the Activity, without to get Exception like
Could not find class 'android.widget.Switch' ...
You can reference Build.VERSION.SDK_INT to inspect the device's API level at runtime. That way, you can add conditional code in your Java classes that will vary its behavior based on the user's API level, like so:
if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH ) {
// Some code that requires API level 14+
} else {
// Code that will run on lower API levels instead
}
For Switch you can try the SwitchCompat library, to make it backwards compatible.
You usually include the Support Library in your project dependencies for that purpose.
(http://developer.android.com/tools/extras/support-library.html)
You can then use new objects (like Fragments for example) with older devices.
Unfortunately, Switch is not among them...
I have issues in my app regarding StrictMode and added the code snippet that basically disables the StrictModeHelper. However, Lint complains about setThreadPolicy() now and proposes to either add
#SuppressLint 'NewApi'
or
#TargetApi(Build.VERSION_CODES.GINGERBREAD)
to the onCreate() event of the view.
Which method is prefered ..or are they basically doing the same?
I have issues in my app regarding StrictMode and added the code snippet that basically disables the StrictModeHelper
Please fix the networking bug.
Which method is prefered ..or are they basically doing the same?
#TargetApi and #SuppressLint have the same core effect: they suppress the Lint error.
The difference is that with #TargetApi, you declare, via the parameter, what API level you have addressed in your code, so that the error can pop up again if you later modify the method to try referencing something newer than the API level cited in #TargetApi.
For example, suppose that, instead of blocking the StrictMode complaints about your networking bug, you were trying to work around the issue of AsyncTask being serialized on newer versions of Android. You have a method like this in your code to opt into the thread pool on newer devices and use the default multithread behavior on older devices:
#TargetApi(11)
static public <T> void executeAsyncTask(AsyncTask<T, ?, ?> task,
T... params) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
}
else {
task.execute(params);
}
}
Having #TargetApi(11) means that if Lint detects that I am using something newer than my android:minSdkVersion, but up to API Level 11, Lint will not complain. In this case, that works. If, however, I modified this method to reference something that wasn't added until API Level 14, then the Lint error would appear again, because my #TargetApi(11) annotation says that I only fixed the code to work on API Level 11 and below above, not API Level 14 and below above.
Using #SuppressLint('NewApi'), I would lose the Lint error for any API level, regardless of what my code references and what my code is set up to handle.
Hence, #TargetApi is the preferred annotation, as it allows you to tell the build tools "OK, I fixed this category of problems" in a more fine-grained fashion.