Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to share Java code between Android and JVM targets (with Kotlin Multiplatform)?

I am trying to share Java code between Android and JVM targets using Kotlin Multiplatform feature (sample project: https://github.com/dmitrykolesnikovich/accessJavaCode-issue)

Simply saying, ":library1" and ":library2" both are Kotlin multiplatform libraries targeting JVM and Android. ":library2" depends on ":library1". They both uses Kotlin and Java. ":library2" is intended to be dependency of 1) Android application and 2) desktop (JavaFX) application. That's why 1) AAR artifact and 2) JAR artifact both are needed (?) - so I use 1) Android target and 2) JVM target for both ":library1" and ":library2".

The problem is that, when I have Java code in ":library1"

public class JavaCode {} // JavaCode.java

And Kotlin code in ":library2" that depends on ":library1"

class AccessJavaCode : JavaCode() // AccessJavaCode.kt

Android target is OK with recognizing Java but JVM target is not:

> Task :library2:compileKotlinJvm FAILED
e: AccessJavaCode.kt: (3, 38): Unresolved reference: JavaCode

In gradle config I define two plugins: kotlin-multiplatform and com.android.library:

apply plugin: "kotlin-multiplatform"
apply plugin: "com.android.library"

kotlin {
    targets {
        jvm()
        android()
    }
    sourceSets {
        jvmMain {
            dependencies {
                api kotlin("stdlib-common")
                api kotlin("stdlib-jdk8")            
            }
        }
        androidMain {
            dependsOn jvmMain
        }
    }
}

android {
    compileSdkVersion 28
    sourceSets {
        main {
            java.srcDirs += "src/jvmMain/kotlin" // Android target recognizes Java with this
            manifest.srcFile "src/androidMain/AndroidManifest.xml"
        }
    }
}

I am pretty sure it’s something simple with my gradle file. Many thanks for your help guys.

like image 688
Dmitry Kolesnikovich Avatar asked Dec 25 '19 13:12

Dmitry Kolesnikovich


1 Answers

~~~~ EDIT ~~~~

Another workaround the issue without losing the ability to generate an android archive .aar for the library1 would be to make a new version of this same library depending on precompiled artefacts of a splitted version of the original library1.

So you would end up with a multi-module gradle project, something like this:

  • library1-jvm with java plugin enabled
  • library1-android with android plugin enabled
  • library1 which will depends on prebuilt library1-jvm.jar and library1-android.aar

You could use whatever you prefer to publish those artefacts, but a local maven repo should work just fine!

That would mean replacing:

kotlin {
    targets {
        jvm()
        android()
    }
    sourceSets {
        jvmMain {
            dependencies {
                 api kotlin("stdlib-common")
                 api kotlin("stdlib-jdk8")
            }
        }
        androidMain {
            dependsOn jvmMain
        }
    }
}

with:

kotlin {
    targets {
        jvm()
        android()
    }
    sourceSets {
        jvmMain {
            dependencies {
                api kotlin("stdlib-common")
                api kotlin("stdlib-jdk8")
                api "com.company:library-jvm:1.0.0" 
            }
        }
        androidMain {
            dependsOn jvmMain
            dependencies {
                api "com.company:library-android:1.0.0"
            }
        }
    }
}

That way, you don't need the java plugin at all in the final library1, because all the java code will already be built in a separate step.

Hence library1 could keep both the JVM and Android targets

~~~~~~~~~~~~~

In order to fix your issue, you need to:

  • Split your build.gradle configuration in order to have one config per library, this will be needed because you cannot enable the java plugin and the android at the same time for the same Gradle project, or you will end up with the following Error: The 'java' plugin has been applied, but it is not compatible with the Android plugins
  • Enable the java plugin in your library1 project if you want your JVM target to recognize your Java source files.
  • The Java source files need to be placed in the sibling directories java of the kotlin source roots.

java source roots

More infos: Kotlin docs for java-support-in-jvm-targets

I also created a pull request solving your issue.

The downside of this approach is that you will not be able to generate an android archive .aar for the library1, but I guess using the java archive .jar in your android project should not be a problem at all.

library1/build.gradle:

apply plugin: "kotlin-multiplatform"

kotlin {
    jvm {
        withJava()
    }

    sourceSets {
        jvmMain {
            dependencies {
                api kotlin("stdlib-common")
                api kotlin("stdlib-jdk8")
            }
        }
    }
}

library2/build.gradle:

apply plugin: "kotlin-multiplatform"
apply plugin: "com.android.library"

kotlin{
    jvm()
    android()

    sourceSets {
        jvmMain.dependencies {
            api project(":library1")
        }
        androidMain {
            dependsOn jvmMain
        }
    }
}

android {
    compileSdkVersion 28
    sourceSets {
        main {
             java.srcDirs += "src/jvmMain/kotlin"
             manifest.srcFile "src/androidMain/AndroidManifest.xml"
        }
    }
}
like image 63
Gomino Avatar answered Oct 23 '22 05:10

Gomino