Is there an annotation that denotes a max Android API version? - android

The annotation #RequiresApi is helpful for requiring that the annotated element is only called on the given API level or higher. However, there does not appear to be a corresponding annotation for requiring that the annotated element is only called on the given API or lower.
For example, given something like:
#RequiresMaxApi(28) // or #DeprecatedSinceAndroid(29)
fun doSomethingWithExternalStorage() {
val dir = Environment.getExternalStorageDirectory() // This is deprecated in API 29
// ...
}
Calling doSomethingWithExternalStorage() from code that does not include a check like:
if (VERSION.SDK_INT < 29) {
doSomethingWithExternalStorage()
}
Without the above API check, doSomethingWithExternalStorage() would display a lint error/warning, similar to how #RequiresApi does.
From my understanding, this is also similar to how #DeprecatedSinceKotlin works.
Is there an existing annotation that meets these requirements, or is there another way to achieve the desired result?

Related

JUnit4 parameterized test with different Android SDK versions

I'd like to test a function having a conditional statement for different Android SDK build versions with some parameters. The function looks like:
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O) {
// do something with parameter(s) and return it;
} else {
// do something with parameter(s) and return it;
}
I can use Parameterized.class runner or JUnitParams to get it run with a set of parameters. I can also create a single test function and can change the SDK_INT field and call the function to test. But I was not able to combine both approaches into the same unit test class.
Is there any annotation to set the SDK for a particular test method? Or what is the best approach to test functions like the above?
A simple yet not elegant solution is to use the Enclosed runner and inner classes changing the SDK_INT field in a function annotated with #BeforeClass.

Android "com.android.internal.util.Predicate" deprecated

I just change my compile sdk version from 27 to 28 , after that am getting an issue because of deprication
com.android.internal.util.Predicate
as google developer forum say i changed it to "java.util.function.Predicate " but that time apply() is not working.
Is there any alternate function for apply()
see the Java SE documentation ...it uses a type parameter & a functional interface:
Interface Predicate<T>
Type Parameters: T - the type of the input to the predicate
Functional Interface: This is a functional interface and can therefore be used as the assignment target for a lambda expression or method reference.
as well as the warning in the Android SDK documentation:
This must not be used outside frameworks/base/test-runner.
for example:
Predicate<Integer> greaterThanTen = (i) -> i > 10;
Predicate<Integer> lesserThanTwenty = (i) -> i < 20;
boolean result = greaterThanTen.and(lesserThanTwenty).test(15);
rather complex methods assigned to the functional interface might provide more sense - because else, this is a quite complicated way of formulating this (without improving the readability too much):
boolean result = 15 > 10 && 15 < 20;

LifecycleObserver produce exception with methods that use newer APIs

My ViewModel class implements LifecycleObserver.
When I call fragment.lifecycle.addObserver(this) it produces exception.
Caused by: java.lang.IllegalArgumentException: The observer class has some methods that use newer APIs which are not available in the current OS version. Lifecycles cannot access even other methods so you should make sure that your observer classes only access framework classes that are available in your min API level OR use lifecycle:compiler annotation processor.
Strange, that firstly it was working fine, but not long ago this exception has appeared. I've found, that audioFocusRequest is cause of this bug.
private val audioFocusRequest by lazy {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setOnAudioFocusChangeListener(this)
.build() else throw RuntimeException("Can't be done for Android API lower than 26")
}
Does anybody know how it can be fixed?
UPD
Tried to use annotationProcessor "androidx.lifecycle:lifecycle-compiler:$lifecycle_version", but got compilation error:
(decided to paste screenshot, because whole logs are quite big)
UPD 2
At the end I've decided to delete audioFocusRequest field and to use old deprecated method - requestAudioFocus(OnAudioFocusChangeListener l, int streamType, int durationHint) instead of recommended requestAudioFocus(#NonNull AudioFocusRequest focusRequest)
It helped me to make code working again, so it can be solution. But I didn't find answer - why this problem had appeared. It strange because code used to be working before.
So problem has been solved but question still stays unanswered
Try to use kapt "androidx.lifecycle:lifecycle-compiler:2.0.0"
The class which implements LifecycleObserver has some method, which has parameters with type that only exist for higher APIs.
Your variables (i guess) and function parameters must exist on all APIs even function is not called (maybe this is requirement for classes who implement LifecycleObserver).
A possible solution is to change function parameter type to Any (kotlin) or Object (Java) and cast it to appropriate type inside function.
I have to remove this set method on SpinnerView: lifecycleOwner = viewLifecycleOwner
I was able to fix this by moving the offending methods into another class, but still called from my LifecycleObserver. After reading the error message again:
Caused by: java.lang.IllegalArgumentException: The observer class has some methods that use newer APIs which are not available in the current OS version. Lifecycles cannot access even other methods so you should make sure that your observer classes only access framework classes that are available in your min API level OR use lifecycle:compiler annotation processor.
It seems as though no methods or objects are allowed in the class extending LifecycleObserver if they don't exist in the device's OS, even if they are wrapped in an SDK version check and never accessed.

Kotlin Extension Functions suddenly require api level 24

I just noticed this lint error:
Call requires API Level 24 (current min is 19) java.util.map#foreach
when I use the extension function forEach on a MutableMap in Kotlin.
This didn't happen when I wrote the line, but its there now.
And I'm not seeing this error on my other machine.
What you are using is not kotlin.collections.MutableMap.forEach.
What you are using seems to be Map.forEach in Java 8.
Refer to this article:
http://blog.danlew.net/2017/03/16/kotlin-puzzler-whose-line-is-it-anyways/
This seems to be a common mistake.
Java 8 is well-supported from Android API level 24.
For short, do not use forEach like map.forEach { t, u -> Log.i("tag", t + u) } (this is using Java 8 API, which does not work for Android API level <= 23).
Use it like map.forEach { (t, u) -> Log.i("tag", t + u) } (this is using Kotlin API).
(Not two parameters. Just one parameter that is a pair.)
This also happens in a list even tho I'm using I need to use:
list.iterator().forEach {}
the key is use iterator() before forEach.
Ran into this because the map a dependency provided was a Java 8 map, so the forEach was linted as the Java 8 version, no matter how I grouped my parameters in the lambda signature (one, two -> vs (one, two) ->). Best solution I could find was using an explicit import with an alias (import kotlin.collections.forEach as kForEach). The alias keeps optimize imports from removing the explicit import.
Map.forEach is supported in Java 8 and its support in Android started from API 24

Using Java reflection vs checking Build.VERSION.SDK_INT

Why should I bother using reflection as discussed here, if I can simply test Android version from Build.VERSION.SDK_INT and conditionally run functions not available on lower API versions?
That article discussed how to get method ID, handle exceptions, etc, which seems more complicated than simply using:
if(Build.VERSION.SDK_INT>=11){
// some Honeycomb code
// example: findViewById(R.id.root).setSystemUiVisibility(View.STATUS_BAR_HIDDEN);
}
This code works fine for me on various devices (2.2 / 3.2 / etc).
Thanks
Your proposal won't work (without reflection) when running on an older Android system, if the code hidden in "// some Honeycomb code" uses Class or Method names that only exist in the Honeycomb API. The root of the problem is that all classes referenced from code are loaded when the class is loaded. You need to use reflection to delay resolution of the code containing Honeycomb references until run-time.
Specifically, if you have a class:
class MyUseOfFeatures {
public void doSomething() {
if (TestIfPhoneHasFancyHoneycombFeature()) {
Object example = android.util.JsonReader(); // JsonReader is new in 3.0
}
}
Then when the JVM (er, DVM?) loads the bytecode for this class it will try and resolve the android.util.JsonReader name when the class is loaded (presumably when your application is loaded).
If you only rely on some behavior of Honeycomb (not any any new classes, methods or fields), then you'd be fine to just test the build number.

Categories

Resources