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

Viewed 1039

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

2 Answers

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