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
Related
I have read the dozens of questions here on SO regarding recycling TypedArrays, but I guess they are a bit too old and written before we could widely use try-with-resource statements, so none of them talk about using the AutoCloseable implementation of the TypedArray, which is present since API Level 31
So the question remains: is this a false positive in Lint?
If anything, that warning should be a minSDK warning if applicable, right?
Can we simply write the following since the full try-with support (if we do it after SDK Level >= 31 check)?
try (TypedArray array = getContext().obtainStyledAttributes(attrs) {
// Do someting
}
// End of method
My guess is yes, as this is the AutoCloseable implementation of TypedArray
So the question remains: is this a false positive in Lint?
No, it is not. Because close method in AutoCloseable interface is not magically called when using try/catch.
Instead you have to use use method and then and only then you can get rid of try/catch like following:
getContext().obtainStyledAttributes(attrs).use({
// Do something
});
But, be aware that use method from TypedArray class is available only since Android 31
If you prefer a backwards compatible solution, you can use use method from androidx.core:core-ktx library.
As TypedArray also provides of a use method you will have to take care of adding the following import:
import androidx.core.content.res.use
I recently came across the deprecation of clipRect(Rect,Region.Op), which I would like to use with DIFFERENCE. This was replaced with clipOutRect(Rect) and thus I implemented:
#Suppress("DEPRECATION")
fun clipOutRect(canvas: Canvas, rect: Rect) =
if (SDK_INT >= O) canvas.clipOutRect(rect)
else canvas.clipRect(rect, DIFFERENCE)
Now this looks like it could be a compatibility method in AndroidX, but for some reason I was not able to figure out, where I could find it exactly.
Is there a class already providing a compatibility method for clipOutRect(Rect)?
Short answer is - no. The only thing related to Canvas in AndroidX is this file: https://github.com/aosp-mirror/platform_frameworks_support/blob/androidx-master-dev/core/core-ktx/src/main/java/androidx/core/graphics/Canvas.kt
Long answer.
First of all, Canvas is passed to view by native code, so it will be awkward to have something like onDrawCompat(canvas: CanvasCompat) in ViewCompat class. And I think there is no reason to do that at all.
Also, it's really not that type of deprecation you should worry about.
For example WifiManager.startScan() is noted with
This method was deprecated in API level 28. The ability for apps to
trigger scan requests will be removed in a future release.
That says Change this code now, or it will be broken year later
That not the case with clipRect, it will be kept for backward compatibility with apps that won't be ever updated for years or even tenth of years. Deprecation warning for this is just like Hey, we have new method with better functionality/name, if you target minimum is API 26 you can use that
Inside a method I run a line which is different for different SDKs. And the lint marker always marks it as an error and suggests me to add a new api annotation for the whole method, but this is only one line of code I think it's pointless to create a new method just for this.
I have this in many places, but for example :
private void methodA(){
...
some code (that is good for both)
...
if(Build.VERSION>SDK_INT >= 16)
pb.setBackground(anim);
else
pb.setBackgroundDrawable(anim);
...
more code
...
}
Window->Preferences->Android->Lint Error Checking:
Change NewApi Severity to Ignore.
In Eclipse, you have to use an annotation or disable the inspection completely.
If you used the new Android Studio, however, you wouldn't have this problem. It will understand that the code under the if block will run only if the API level is >= 16, and will check the methods accordingly.
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.
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.