Android Kotlin call different and deprecated constructor conditionally - android

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.

Related

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.

Is there better way for handle kotlinx serialization?

I use kotlinx.serialization on Kotlin native project, I a defined Super class for my models and all of the models extends from it.
I defined a function to called toJSON() for serialize variables and fields inside model that all of class models have it.
#Serializable
open class Model {
fun toJSON(): String = JSON.stringify(this);
}
And I created a subclass
class Me : Model() {
var name:String = "Jack";
}
but when I invoke JSON.stringify(this), IDE get a Warning to me:
This declaration is experimental and its usage must be marked with '#kotlinx.serialization.ImplicitReflectionSerializer' or '#UseExperimental(kotlinx.serialization.ImplicitReflectionSerializer::class)'
I paid attention and I used #ImplicitReflectionSerializer annotation while not worked.
Where is my problem?
This is discussed here. It's the particular overload you're using which is still experimental. So your options are either to use the other overload (which takes in a serializer) or to use one of the annotations mentioned in the error message. If you look at the answer to the question I linked (and the comments following it), you'll see it talks about using #UseExperimental and where it should be used.

How much of a mock is a Mockito mock?

This is a serious question, I promise. I've spent the last 2 hours reading as many definitions of Mock that I could find and none explain this to me.
I've got a class I want to test and that class requires a mapper class as part of it's primary constructor:
open class PoiListViewModel #Inject constructor(
private val mapper: PoiMapper
) : ViewModel() {
In my unit test I have the following code:
//Mock objects needed to instantiate the class under test
#Mock lateinit var mapper: PoiMapper
// Class being tested
lateinit var poiListViewModel: PoiListViewModel
#Before
fun setup() {
MockitoAnnotations.initMocks(this)
poiListViewModel = PoiListViewModel(mapper)
}
My question to you all smart developers is, what exactly is a mock? And specifically how much of my original class does it replicate?
I'll tell you my assumed definition. A mock is a fake stand-in class that stands in for my real class, but that it does nothing except keep track of what method calls get sent to it. If I want the mock to have any functionality I need to stub that functionality in.
At least that's my ignorant view of mocks. But I'm apparently wront because in my unit test, my "mock" mapper class seems to be an actual mapper class. If I debug my unit test I see it walk through all the code of my mapper class. I see it returning converted data.
Here's the mapper class code (if it matters):
open class PoiMapper #Inject constructor() {
fun mockTest(num: Int): Int{
return num *23
}
fun mapToPresentation(domainModel: Poi_Domain): Poi_Presentation {
var test = 3
var results = mockTest(test)
return Poi_Presentation(domainModel.id,domainModel.name,domainModel.description,
domainModel.img_url,domainModel.latitude,domainModel.longitude,domainModel.imgFocalpointX,
domainModel.imgFocalpointY,domainModel.collection,domainModel.collectionPosition,
domainModel.release,domainModel.stampText)
}
}
Can someone explain it to me, how much of a mock is a Mockito mock? Did I instantiate the mocks incorrectly? Can someone give me a better way to think of mocks so I can wrap my head around all this?
Your understanding of mocks is correct. You're bumping into Kotlin's final-by-default behavior, as an implementation detail of Mockito.
Mockito mocks (as distinct from Mockito spies) are defined to take the place of your class instance. Unless you've stubbed them to return otherwise, they record all interactions and return default dummy values (zero, an empty string, an empty list, null, etc). You can confirm that they collaborated correctly with your system-under-test by stubbing return values (when/thenReturn), stubbing specific behaviors for methods (when/thenAnswer), or by checking that certain methods were called (verify) and retrieving the specific instances they were called with (ArgumentCaptor).
Under the hood, Mockito achieves this by generating a subclass of the class you're mocking, and overriding all methods to delegate to Mockito's internal handler. This is what gives Mockito the power to override the behavior silently: Your system-under-test thinks it's calling your dependency, but your code is using Java's virtual method dispatch to override your dependency's behavior.
Here's the trick: Java methods are virtual by default, unless you mark them final. In Kotlin, functions are closed by default, unless you mark them open. (I'm going to keep calling them final, because that's the definition at play in the JVM or Android Dexer, which is reading the bytecode that Kotlin generates anyway.) When the virtual machine is sure of a reference's type based on static typing, and you're calling a final method, the compiler is allowed to inline that implementation's code (JLS 8.4.3.3) because you've asserted that the method cannot be overridden and any code that tries to override it will fail at compilation. Mockito's generated code isn't compiled this way, and Mockito can't detect this case.
In your case, mapToPresentation is not open, so the compiler sees it as final and does not keep the virtual method dispatch that would let Mockito override the behavior. Your definition of mocking is right, and your code would be right, except that Kotlin is closed-by-default where Java is open-by-default. Besides, you really do rely on the function being open, because you'd like it to be called with a different implementation than the one in the function.
Trivially, you could just make all functions open that you intend to override, or use Mockito 2.1+'s built-in feature to mock final methods. However, as a general best practice you want an object that behaves like a PoiMapper even if it doesn't follow your specific PoiMapper implementation. That might be a good reason for an interface/impl split, such that your class PoiListViewModel takes a PoiMapper interface. In production you can provide a PoiMapperImpl as you have, but Mockito can generate an arbitrary implementation of the PoiMapper interface without worrying about whether PoiMapperImpl and its functions are open or closed.
Have you added the annotation
#RunWith(MockitoJUnitRunner.class)
to your test class?

Kotlin do not call super

I have a class that extends another, but in this class I do not want to call the super constructor.
How can I solve it?
Here is a snipet of my code
class SubarticlePagerAdapter(fragmentManager: FragmentManager, context: Context, var selectedArticleName: String) : ArticlePagerAdapter(fragmentManager, context) {
var subarticleDao: ArticleDao
var itemCount = 0
init {
ApplicationHelper().getApplication(context).appComponent.inject(this)
subarticleDao = ApplicationHelper().getApplication(context).subarticleDaoSession.articleDao
initBundles(context)
}
override fun initBundles(context: Context?) {
}
}
My problem, when this constructor is called, parent class constructor run first, and initBundles() will be called from there, but at that time subarticleDao and selectedArticleName are not set and I get exception.
TL;DR
I'd advise you to move the code from the init block to the initBundles function and use your variables there after initialization. Then there would be no need to avoid calling the superclasses constructor.
Extensive Answer
I think you should think about what you want to do with your design. Working around the idioms of a language is not very often a good idea or a sign of good design - at least when kotlin is your language :)
What you did with your code (overriding a - possibly abstract - method, initBundles from your superclass is pretty much the template method pattern. So it seems to me the purpose of initBundles is to let subclasses customize parts of the initialization... What basically is what you do in your init block.
EDIT: As Paul pointed out in the comments, you can't use the member selectedArticleName before your base classes initialization has finished. So if the base class calls initBundles during its initialization, then properties in the subclass won't be initialized as also stated at Paul's link.
Since in the snippet you don't use selectedArticleName, you could just move your initialization stuff to the initBundles function and init your subarticleDao there.
However, if you need to use your subclasses properties at that point, I'd really advise you to rethink your design. There should be several ways to solve this, but to decide what would suits your requirements best one would need further insight into the intentions you have with your design.

Android studio convert to Kotlin: Use #JvmStatic in some cases

I have been using Kotlin over Android for quite intensively. It does make programming fun again.
Still, in some cases (mostly util classes where the name should be short and handy), when automatically converting Java to Kotlin, I would love to have an option to use #JvmStatic on static methods rather than converting callers to MyClass.Companion.Bar.
That is, in some specific cases, it would be nice to have
public static foo(Barian bar)
converted to
#JvmStatic
fun foo(bar:Barian)
so I can maintain the short calling syntax from Java:
MyClass.foo(bar)
rather than
MyClass.Companion.foo(bar)
Obviously, in most cases I agree it's bad manners for many reasons such as future compatibility, non-Kotlin spirit and many more, but in a few cases it can keep Java code (that uses my classes) shorter.
You don't need to specify the Companion-namespace explicitly, when you decalre your "static" method like this:
class MyClass {
companion object {
fun foo() {}
}
}
In this case you still can call it via:
MyClass.foo()
But nevertheless having static methods is not a Kotlin-idiomic way and should be avoided by using other features of this language.

Categories

Resources