Android unit test, super deprecated, replacement? - android

The android unit testing documents say to test with junit like this
public SpinnerActivityTest() {
super("com.android.example.spinner", SpinnerActivity.class);
} // end of SpinnerActivityTest constructor definition
http://developer.android.com/tools/testing/activity_test.html#InstallCompletedTestApp
Their example uses Android 2.1 though.
My app is using Android 4.2 although is backwards compatible.
What has the super class been replaced with? How should my constructor be written

You will need to use the call:
super (SpinnerActivity.class);
It's specified in the Android reference docs.
ActivityInstrumentationTestCase2(String pkg, Class<T> activityClass)
This constructor was deprecated in API level 8. use ActivityInstrumentationTestCase2(Class) instead
Use this instead.
ActivityInstrumentationTestCase2(Class<T> activityClass)
Creates an ActivityInstrumentationTestCase2.
Parameters
activityClass The activity to test. This must be a class in the instrumentation targetPackage specified in the AndroidManifest.xml

The API level 24 recommendations are to use the Testing Support Library. The InstrumentationRegistry.getTargetContext() method is probably what you want if you were using getContext().

Related

Android Kotlin call different and deprecated constructor conditionally

I have an Android app written in Kotlin with a class BaseKeyListener that extends DigitsKeyListener. My minimum SDK version is 21. The class currently is calling a deprecated constructor. However the new constructor is only available from API level 26 and upwards.
How would I call the constructor conditionally based on API level?
I basically posted the same problem a while ago for Android but the solution doesn't seem to work in Kotlin.
In Kotlin my class now looks like this:
// primary constructor 'DigitsKeyListener' shows lint warning about deprecation.
abstract class BaseKeyListener() : DigitsKeyListener() {
}
If I apply the solution for the Android question I get this code:
abstract class BaseKeyListener : DigitsKeyListener {
// still results in deprecation warning
constructor() : super()
}
An alternative solution was also provided were I had to make the constructors private and implement a newInstance pattern. However I can't use that solution because there are other classes that inherit from the BaseKeyListener and the BaseKeyListener is also abstract.
The only thing I can think of is this:
abstract class BaseKeyListener : DigitsKeyListener {
constructor()
#RequiresApi(Build.VERSION_CODES.O)
constructor(locale: Locale) : super(locale)
}
But as a result I would have to define two constructors for each subclass. And were I use the class I would have to add a condition every time while the Locale that we use is the same.
Unfortunate result:
open class AmountKeyListener : BaseKeyListener {
constructor() : super()
#RequiresApi(Build.VERSION_CODES.O)
constructor(locale: Locale) : super(locale)
}
// usage of the keyListener
editText.keyListener = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) KeyListenerUtil.AmountKeyListener(
MY_LOCALE) else KeyListenerUtil.AmountKeyListener()
The ideal solution should be to assign the AmountKeyListener on a single line and the BaseKeyListener should know when to use our custom locale 'MY_LOCALE'
editText.keyListener = KeyListenerUtil.AmountKeyListener()
How to solve this problem?
The Java solution you linked basically is just ignoring the deprecation and exclusively using the deprecated constructor. I think your last solution is the best one. Its usage is no worse than if you were using DigitsKeyListener directly--you'd still have to be checking the SDK version.
One minor issue I see above is that your first constructor implicitly calls the empty super constructor, and thereby avoids the deprecation warning through what is essentially a language hack. Really, this seems to me to be a bug in the code inspector of Kotlin. I think it would be more appropriate to explicitly call the super constructor and also deprecate this constructor in your own class. So I would make it look like this:
abstract class BaseKeyListener : DigitsKeyListener {
#Suppress("DEPRECATION")
#Deprecated("Use the constructor with a locale if on SDK 26+.")
constructor(): super()
#RequiresApi(Build.VERSION_CODES.O)
constructor(locale: Locale) : super(locale)
}
This doesn't functionally change how it works, but without deprecating your bare constructor, it becomes very easy to use the deprecated version of DigitsKeyListener everywhere by accident.
And at the usage site, although painful, it would look like you have it above, except you would also put #Suppress("DEPRECATION") right before that line to acknowledge the deprecation warning.

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.

Android LinkedHashMap.eldest() method is NOT available

I see the LinkedHashMap.eldest() method when attaching the source but cannot use it.
Any idea why it isn't available?
It is a hidden method - invokable using reflection, but isn't part of the public API and therefore likely to change between versions of android
https://android.googlesource.com/platform/libcore/+/a47f800/luni/src/main/java/java/util/LinkedHashMap.java line 169 the #hide annotation stops it from being visible to the public
Because there no such method in this class and any super type:
LinkedHashMap
HashMap
AbstractMap

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.

Is it possible to bypass constructors when instantiating objects in Android

Do Android have any way to instantiate objects without calling any of its constructors?
In Java, Sun have sun.reflect.ReflectionFactory.getReflectionFactory().newConstructorForSerialization(), in .Net we have System.Runtime.Serialization.FormatterServices.GetUninitializedObject() but I was not able to find anything like that in the Android platform.
After looking into Android source code we found a way to achieve this by using ObjectInputStream#newInstance() static method.
private Object newInstanceSkippingConstructor(final Class clazz) throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
Method newInstance = ObjectInputStream.class.getDeclaredMethod("newInstance", Class.class, Class.class);
newInstance.setAccessible(true);
return newInstance.invoke(null, clazz, Object.class);
}
You can do this from native code with the JNI AllocObject function. See the JNI Spec. Calling out to native code is going to be more expensive than calling a no-op constructor, but probably cheaper than calling a constructor that throws an exception.
I don't know if there's another way to do it. Nothing is leaping out at me.
I don't believe so, however the Android platform does contain the Java Reflection API in java.lang.reflect.* so anything that is possible using the Java Reflection API is possible in Android
I found out a library that can handle OBJ init with constructor bypass
http://objenesis.org/index.html
And they have different methods for different APIs.
There is a slight difference in their API 17 and 18+ impl as in the current suggested answer. Also Android N+ is using something called Unsafe that should be implemented by new JVMs http://mishadoff.com/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/

Categories

Resources