Kotlin Multiplatform Expected Annotations Returns `This class does not have a constructor` with neo4j actual typealias - kotlin

I have a multiplatform project with a modal class, User.
User.kt
class User {
val id = -1
val username = ""
val age = -1
val nickname = ""
}
I also have expected and actual annotations
Annotation.kt [Common Module]
expect annotation class NodeEntity
expect annotation class Id
expect annotation class GeneratedValue
More over, I have their actual implementation
Annotation.kt [JVM Module]
actual typealias ValueFor = org.neo4j.ogm.annotation.ValueFor
actual typealias NodeEntity = org.neo4j.ogm.annotation.NodeEntity
actual typealias Id = org.neo4j.ogm.annotation.Id
actual typealias GeneratedValue = org.neo4j.ogm.annotation.GeneratedValue
actual typealias Relationship = org.neo4j.ogm.annotation.Relationship
I then went back and annotated my User.kt
#NodeEntity
class User {
#Id
#GeneratedValue
val id = -1
val username = ""
val age = -1
val nickname = ""
}
But When I compile it, I get this error
Task :compileKotlinJvm FAILED
e: ...User.kt: (13, 2): This class does not have a constructor
e: ...User.kt: (21, 6): This class does not have a constructor
e: ...User.kt: (22, 6): This class does not have a constructor
What Am I doing wrong?
N:B. Attempts done
Made the expected annotations have a constructor [no success]
Made the expected annotations match with a constructor [ERROR: Parameter ''{0}'' has conflicting values in the expected and actual annotation]
FYI: My build.gradle already has the noArg in place, so that the User.kt class is compiled with a no argument public constructor

Your expect annotations probably need explicit parenthesis.
expect annotation class SharedImmutable()
actual typealias SharedImmutable = kotlin.native.SharedImmutable
https://github.com/touchlab/Stately/blob/4b17057ad5d55f51f4ccf971cf79e51585ad2324/src/commonMain/kotlin/co/touchlab/stately/annotation/Annotations.kt#L26

I experienced a similar issue, but seemingly like the OP, I already included the explicit parenthesis. My particular issue had to do with the Java Library, in the Java Source Set, not being available to another Gradle Sub-project that was depending on it.
TL;DR
Assert that you are properly exposing the platform-specific dependencies. For instance, properly using implementation and api in the build.gradle files.
Elaborating on my scenario
I had a Gradle multi-project build:
Project
AppSubProject
LibrarySubProject
Where AppSubProject depended on LibrarySubProject. Both Gradle Sub-projects were Kotlin Multi-platform Modules.
In LibrarySubProject, there was an exposed Annotation Class:
Common Source Set:
expect annotation class Inject()
JVM Source Set:
actual typealias Inject = javax.inject.Inject
The Kotlin Common Inject annotation was available to AppSubProject since it depended on the LibrarySubProject.
AppSubProject/build.gradle:
...
commonMain {
dependencies {
implementation project(":LibrarySubProject")
...
The Cause of the Issue
In the LibrarySubProject/build.gradle file I wasn't exposing the JVM dependency:
...
jvmMain {
dependencies {
implementation "javax.inject:javax.inject:1"
...
As you can see, I was using implementation instead of api. So when I used the annotation on a constructor of a class in AppSubProject:
class Example #Inject constructor()
and when I built the AppSubProject, it couldn't resolve the JVM dependency, and was cryptically giving me the following error:
e: Example.kt: This class does not have a constructor
The Solution
The solution was simply to expose the JVM dependency so that it could be resolved in the other module. So changing the implementation to api resolved the issue.
...
jvmMain {
dependencies {
api "javax.inject:javax.inject:1"
...
Summation
If you experience this issue, assert the following:
The Kotlin Common Annotation Class explicitly provides the constructor parenthesis as noted in this answer
All necessary platform-specific dependencies are exposed properly

Related

How do you set up a property in a custom gradle task?

I want to write a task that takes a directory from , does something with the files in it and writes the result into some other directory to.
I've been led to believe this was the way to define such a task (kotlin dsl):
package my.app
abstract class FooBarTask : DefaultTask() {
#get:InputDirectory
abstract val from: Property<Directory>
#get:OutputDirectory
abstract val to: Property<Directory>
#TaskAction
fun doSomething() {
println("Hakuna Matata")
}
}
now how do I set the from and to value in a groovy-based build.gradle?
def myTask = tasks.register('myTask', FooBarTask) {
from = layout.projectDirectory.dir("foo")
to = layout.buildDirectory.dir("bar")
}
this results in
Could not create task ':my-subproject:myTask'.
> Please use the ObjectFactory.directoryProperty() method to create a property of type Directory.
and it shouldn't.
How do you correctly define a directory property in a custom task?
Gradle has the specialized DirectoryProperty, that offers some additional functionality, compared to the plain Property<Directory> which is one of the implemented interfaces. So this specialized type should be used when declaring directory inputs/outputs.
I'm actually not a 100% sure what caused the error you saw.

Kotlin - IntelliJ cannot resolve the reference (from synthetic class)

I am using Kotlin and I have the kotlinc compiler plugin (using arrow-meta library) in place which changes the .class by adding for example new properties or new method etc during kotlin compilation time. for example, the original source Kotlin A.kt is like below
#MetaData
data class A (val x: String, val y: String)
after applying compiler plugin, the .class will be altered to (from source perspective), basically I will add implicitly MetaData into the primary constructor for all class so long as it is with annonation #MetaData in place, plus a new method fun getMetaData() generated
data class A(val x: String, val y:String, val myMeta: MetaData) {
fun getMetaData() {
//some logic check
return myMeta
}
}
now when it comes to use the new synthetic "class" manipulated as below, IntelliJ complains it cannot find resolve A (it has only the constructor with 2 parameters not 3) and cannot resolve the the synthetic method getMetaData() either.
val x = A("ba", "fo", MetaData(..))
val y = x.getMetaData()
can somebody shed some light on it?
I know lombok seems no problem with it after adding its #Getter annotation for example into Java source code, IntelliJ can recognize its getXXX method (which is generated by lombok).
I don't know how to implement the same for my case for kotlin language. please include the detailed steps if possible.

Null property provided by Gradle when using custom plugin

I'm trying to follow the Gradle custom plugin documentation to create a plugin that can be configured.
My plugin code:
interface MyExtension {
var myValue: Property<String>
}
class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
val extension = project.extensions.create<MyExtension>("myExt")
}
}
in build.gradle.kts:
plugins {
`java-library`
}
apply<MyPlugin>()
the<MyExtension>().myValue.set("some-value")
Running this will give
Build file '<snip>/build.gradle.kts' line: 6
java.lang.NullPointerException (no error message)
Turns out the the<MyExtension>().myValue is null, so the set call fails. How do I do this correctly? Did I miss something in the documentation, or is it just wrong?
The documentation is not wrong. Properties can be managed by either you or by Gradle. For the latter, certain conditions have to be met.
Without managed properties
If you want to be completely in charge, you can instantiate any variables you declare yourself. For example, to declare a property on an extension that is an interface, it could look like this:
override fun apply(project: Project) {
val extension = project.extensions.create("myExt", MyExtension::class.java)
extension.myValue = project.objects.property(String::class.java)
}
Or you could instantiate it directly in the extension by making it a class instead:
open class MessageExtension(objects: ObjectFactory) {
val myValue: Property<String> = objects.property(String::class.java)
}
However, a property field is not really supposed to have a setter as the property itself has both a setter and a getter. So you should generally avoid the first approach and remove the setter on the second.
See here for more examples on managing the properties yourself.
With managed properties
To help you reduce boilerplate code, Gradle can instantiate the properties for you with what is called managed properties. To do use these, the property must not have a setter, and the getter should be abstract (which it implicitly is on an interface). So you could go back to your first example and fix it by changing var to val:
interface MyExtension {
val myValue: Property<String> // val (getter only)
}
Now Gradle will instantiate the field for you. The same thing works for abstract classes.
Read more about managed properties in the documentation here.

Kotlin parcelize issue with gson

I am using #parcelize for gson
Here is my class
#Parcelize
data class CommunityModel(#SerializedName("post") val post: PostModel,
#SerializedName("is_liked") val isLiked: Boolean,
#SerializedName("post_like") val postLike: QuestionModel,
#SerializedName("polling_options") val pollingOptions: List<PollingModel>,
#SerializedName("post_polled") val postPolled: Boolean) : Parcelable
I got error Unable to invoke no-args constructor for class. Register an InstanceCreator with Gson for this type may fix this problem..
But this error only presents on older android versions (below 5.0)
I tried implementing default constructor :
constructor: this(PostModel(), true, QuestionModel(), emptyList(), true)
But it gave me java.lang.VerifyError instead
I am using retrofit2 with rxjava2 and gson converter Version 2.3
My kotlin version is 1.1.51
Is it known bug? Or did I do something wrong?
No-arg compiler plugin
The no-arg compiler plugin generates an additional zero-argument constructor for classes with a specific annotation.
The generated constructor is synthetic so it can’t be directly called from Java or Kotlin, but it can be called using reflection.
This allows the Java Persistence API (JPA) to instantiate the data class although it doesn't have the zero-parameter constructor from Kotlin or Java point of view (see the description of kotlin-jpa plugin below).
Using in Gradle
The usage is pretty similar to all-open.
Add the plugin and specify the list of annotations that must lead to generating a no-arg constructor for the annotated classes.
buildscript {
dependencies {
classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlin_version"
}
}
apply plugin: "kotlin-noarg"
Source https://kotlinlang.org/docs/reference/compiler-plugins.html

What is legitimate way to get annotations of a pure Kotlin property via reflection, are they always missing?

I'm trying to get annotations from Kotlin data class
package some.meaningless.package.name
import kotlin.reflect.full.memberProperties
annotation class MyAnnotation()
#MyAnnotation
data class TestDto(#MyAnnotation val answer: Int = 42)
fun main(args: Array<String>) {
TestDto::class.memberProperties.forEach { p -> println(p.annotations) }
println(TestDto::class.annotations)
}
I need to process class annotation to make a custom name serialization of GSON however no matter how I declare annotation class it never gets detected
The program always outputs
[]
[#some.meaningless.package.name.MyAnnotation()]
which means only class level annotations are present
Ok,
it seems that the culprit was, that Kotlin annotations have default #Target(AnnotationTarget.CLASS) which is not stressed enough in documentation.
After I added #Target to the annotation class it now works properly
#Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY)
annotation class MyAnnotation()
Now it prints out
[#some.meaningless.package.name.MyAnnotation()]
[#some.meaningless.package.name.MyAnnotation()]
As a side affect it will force the compiler to check that the annotation is applied as required, in current version of Kotlin, if explicit #Targetis not present only class level annotations are kept but no validity checks performed.
As Kotlin reference said as below:
If you don't specify a use-site target, the target is chosen according to the #Target annotation of the annotation being used. If there are multiple applicable targets, the first applicable target from the following: param > property > field.
To make the annotation annotated on a property, you should use site target, for example:
#MyAnnotation
data class TestDto(#property:MyAnnotation val answer: Int = 42)
However, annotations with property target in Kotlin are not visible to Java, so you should double the annotation, for example:
#MyAnnotation // v--- used for property v--- used for params in Java
data class TestDto(#property:MyAnnotation #MyAnnotation val answer: Int = 42)