Annotation class does not validate input as enum in Kotlin - kotlin

I'd like to create annotation instead of enum use it for when statement
#Retention(AnnotationRetention.SOURCE)
#IntDef(SELECT.toLong(), WEAR.toLong(), BAND.toLong())
annotation class CurrentState
companion object {
const val SELECT = 0
const val WEAR = 1
const val BAND = 2
}
private fun handleFragment(#CurrentState state:Int) {
val fragment:Fragment =
when(state){
SELECT -> SelectDeviceFragment.newInstance()
WEAR -> ConnectWatchFragment.newInstance()
BAND -> SelectDeviceFragment.newInstance()
}
From my understanding, this code should prevent me from performing following code:
handleFragment(5)
and when statement should not ask me to add else statement, as expected from enum
What I'm doing wrong or misunderstand?

From https://discuss.kotlinlang.org/t/intdef-and-stringdef-not-being-checked-at-compile-time/7029:
This checking doesn’t come from the compiler, but from Android lint. Work to make android lint language independent is being done, but if I’m not mistaken you’ll need a newer version of Android Studio for it.
And Android Studio 3.1 blog post mentions lint checks for Kotlin as a feature (though it doesn't say whether this check specifically is supported).

The #IntDef annotation is part of the Android framework. The compiler is not aware of any specific semantics of this annotation, and is not able to use it for checking the exhaustiveness of when statements.
Moreover, even with the annotation, you can call handleFragment(5). Such code will not be a compiler error, it will only be reported as a lint warning.

Related

How can I tell the Kotlin compiler that a Java method will never return null?

I don't or can't modify the Java source code. The goal to configure just the Kotlin compiler to know what is nullable and what isn't.
You can specify the type manually if you know something will never be null. For example, if you have the following Java code:
public static Foo test() {
return null;
}
and you call it in Kotlin like this:
val result = Foo.test()
then result will have a type of Foo! by default – which means it can be either Foo or Foo?.. the compiler doesn't have enough information to determine that.
However, you can force the type manually:
val result: Foo = Foo.test()
// use "result" as a non-nullable type
Of course, if at runtime that is not true, you'll get a NullPointerException.
For reference, please check the documentation.
I don't know of a way to configure the compiler for this, but IntelliJ IDEA has a feature that allows you to add annotations to code via an XML file called external annotations.
You can add the Jetbrains #Nullable and #NotNull annotations to library code, but when I've tried it, it only results in compiler warnings rather than errors when you use incorrect nullability in your code. These same annotations generate compiler errors when used directly in the source code. I don't know why there is a difference in behavior.
You can use extension functions for this. If you have a method String foo() in the class Test, you can define the extension function
fun Test.safeFoo(): String = this.foo()!!
The advantage is that the code is pretty obious.
The disadvantage of this approach is that you need to write a lot of boiler plate code. You also have to define the extension function in a place where all your modules or projects can see it. Also, writing that much code just to avoid !! feels like overkill.
It should also be possible to write a Kotlin compiler extension which generates them for you but the extension would need to know which methods never return null.

Suppress DeprecationLevel.ERROR in Kotlin

I’m using DeprecationLevel.ERROR in my APIs:
#Deprecated(
message = "moved to def()",
replaceWith = ReplaceWith(expression = "def()"),
level = DeprecationLevel.ERROR)
fun abc() = def()
I want a test to ensure callers see this replacement. For example, my test should fail to compile if I accidentally delete the abc() method.
But I can’t find a way to make this compile:
#Test
#Suppress("something")
fun deprecatedAbc() {
abc()
}
For example, #Suppress("DEPRECATION") doesn’t work. Is there something that does?
According to DefaultErrorMessages, you can use #Suppress("DEPRECATION_ERROR").
According to the documentation of #Deprecated:
To help removing deprecated API gradually, the property level could be used. Usually a gradual phase-out goes through the "warning", then "error", then "hidden" or "removed" stages:
First and by default, DeprecationLevel.WARNING is used to notify API consumers, but not to break their compilation or runtime usages.
Then, some time later the deprecation level is raised to DeprecationLevel.ERROR, so that no new Kotlin code can be compiled using the deprecated API.
Code does not compile by design -- and #Suppress only suppresses warnings, not errors (see doc).
So, what you are trying to achieve is not possible. Also, it seems to me as if you're testing a Kotlin feature, rather than your business logic. You might instead try to test the behavior of the replacement.

Generate UUID on kotlin-multiplatform?

Are there any kotlin-multiplatform common functions to get a UUID/GUID?
// ideally something like this
val newUUID = UUID() // "1598044e-5259-11e9-8647-d663bd873d93"
println("newUUID = $newUUID")
I'd prefer not to make separate Android and iOS versions using expect-actual.
As per the Kotlin multiplatform documentation, you can make an expect/actual function to use the Android (java) and iOS (NSUUID) specific implementations:
// Common
expect fun randomUUID(): String
// Android
import java.util.*
actual fun randomUUID() = UUID.randomUUID().toString()
// iOS
import platform.Foundation.NSUUID
actual fun randomUUID(): String = NSUUID().UUIDString()
That one may work
https://github.com/benasher44/uuid
The sources of the project use the Kotlin Multiplatform project to implement the UUID library. See https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html for more details

Jinq in Kotlin - how to convert lambda into java SerializedLambda?

Can I have serializable lambda in Kotlin? I am trying to use Jinq library from Kotlin, but it requires serializable lambdas. Is there any syntax that makes it possible?
Update:
My code:
var temp=anyDao.streamAll(Task::class.java)
.where<Exception,Task> { t->t.taskStatus== TaskStatus.accepted }
.collect(Collectors.toList<Task>());
I am getting this error:
Caused by: java.lang.IllegalArgumentException:
Could not extract code from lambda.
This error sometimes occurs because your lambda references objects that aren't Serializable.
All objects referenced in lambda are serializable (code results in no errors in java).
Update 2
After debugging it seems that kotlin lambda isn't translated into java.lang.invoke.SerializedLambda which is required by Jinq to get information from. So the problem is how to convert it to SerializedLambda.
I'm the maker of Jinq. I haven't had the time to look at Kotlin-support, but based on your description, I'm assuming that Kotlin compiles its lambdas into actual classes or something else. As such, Jinq would probably need some special code for cracking open Kotlin lambdas, and it may also need special code for handling any unusual Kotlin-isms in the generated code. Jinq should be capable of handling it because it was previously retrofitted to handle Scala lambdas.
If you file an issue in the Jinq github about it, along with a small Kotlin example (in both source and .class file form), then I can take a quick peek at what might be involved. If it's small, I can make those changes. Unfortunately, if it looks like a lot of work, I don't think I can really justify putting a lot of resources into adding Kotlin support to Jinq.
I have no experience on Jinq, but according to the implementation in GitHub and my experience of using Java Library in Kotlin.
ref: https://github.com/my2iu/Jinq/blob/master/api/src/org/jinq/orm/stream/JinqStream.java
You can always fall back to use the native Java Interface in Kotlin.
var temp = anyDao.streamAll(Task::class.java)
.where( JinqStream.Where<Task,Exception> { t -> t.taskStatus == TaskStatus.accepted } )
.collect(Collectors.toList<Task>());
// Alternatively, You you can import the interface first
import org.jinq.orm.stream.JinqStream.*
...
// then you can use Where instead of JinqStream.Where
var temp = anyDao.streamAll(Task::class.java)
.where(Where<Task,Exception> { t -> t.taskStatus == TaskStatus.accepted } )
.collect(Collectors.toList<Task>());
Or make a custom extension to wrap the implementation
fun JinqStream<T>.where(f: (T) -> Boolean): JinqStream<T> {
return this.where(JinqStream.Where<T,Exception> { f(it) })
}
Disclaimer: The above codes have not been tested.

Java modifies kotlin class content to null

Kotlin has a brilliant null checks at compile time using separation to "nullable?" and "notnullable" objects. It has a KAnnotator to help determine that objects coming from Java are nullabale or not. But what if some part of not-nullable class is changed?
Consider we have a cat, which certainly has a head that can meeew. When we ask the cat to mew, it mews with its head:
package org.cat
class CatHead(){
fun mew(){
println("Mew")
}
}
class Cat(){
var head = CatHead()
fun mew(){
head.mew()
}
}
fun main(args:Array<String>){
val cat = Cat()
cat.mew()
}
Now lets add to this pretty picture a JAVA cat maniac (CatManiac.java), who cuts off a cats' heads as he gets:
import org.cat.*;
public class CatManiac {
public static void cutCatHead(Cat cat){
cat.setHead(null);
}
}
So if we pass a cat to a maniac, he definitely cuts its head. And how cat says mew without a head?
fun main(args:Array<String>){
val cat = Cat()
CatManiac.cutCatHead(cat)
cat.mew()
}
We get a runtime error inside kotlin because of null, at point of calling cat.mew():
Exception in thread "main" java.lang.IllegalArgumentException".
I believe it is a pretty common problem we can get while using Java libraries in Kotlin. Is there any way or any plans to warn/fight with that?
P.S. Something like KAnnotator looking for such things in your code?
There are checkers for Java code that will be extended for Kotlin code eventually.
For example, the one in IntelliJ will warn you about the problem right now. It's a part of the open-source Community Edition, so you can try it for yourself.
Meanwhile note that Kotlin gives you a great advantage over Java: it fail right at the point where the error is introduced. It's hard to pass a null through many Kotlin calls and only then get a failure. In Kotlin you mostly get a run-time error right where the illegal null is first introduced.