Sean Dawson
Sean Dawson

Reputation: 5786

Share a Kotlin module with an Android and Desktop project

I have a game that I am working on that uses the LibGDX game framework. Currently the platforms I am targeting are Desktop (PC, Mac, Linux) via a platform independent jar and Android.

The project is hosted at https://github.com/NoxHarmonium/project-whiplash Feel free to take a look if you need to.

The bulk of the code is in a module called core and is written entirely in Kotlin. This module is linked into both the Desktop and Android projects.

This works fine for Android versions 7.1+ and for desktop. For all other versions of Android I get a pile of java.lang.NoClassDefFoundError exceptions on anonymous functions such as this:

val objectObservable = this.observableCache.computeIfAbsent(assetRef, fun(assetRef: AssetRef): Observable<T> {
  return Async.start(fun(): T {
    ...
  }).observeOn(this.eventLoopScheduler)
})

Exception Sample:

java.lang.NoClassDefFoundError: com.projectwhiplash.utils.assets.LibGdxDataManager$objectMapFromYaml$objectMapObservable$1

It seems to be caused by an incompatibility with the JVM that Kotlin targets by default (1.8) and the JVM level that older versions of Android support (1.6). I could be wrong but this explains why the latest version of Android works as it supports a later version of the JVM.

The solution should be as simple as forcing Kotlin to emit JVM byte code as version 1.6 but I can't seem to work it out. If you compile Kotlin directly into an Android, this seems to be handled by using the kotlin-android Gradle plugin. Unfortunately I can't use this plugin for the core module because it should not have any Android dependencies.

I tried to override the JVM version using the build settings mentioned at https://kotlinlang.org/docs/reference/using-gradle.html#compiler-options like this:

compileKotlin { kotlinOptions { jvmTarget = "1.6" } }

However, it did not seem to work no matter which Gradle file I placed it in. In fact I get a "Cannot resolve symbol 'kotlinOptions'" error shown by Intellij when I try it. It is possible that the Kotlin team have changed something and the documentation has not been updated.

I can override the Kotlin settings manually in the Intellij module settings but it gets overridden every time I sync the gradle project and is not a good long term solution. The project is designed to be IDE independent.

Does anyone know how I could set up the core module for max compatibility with older versions of Android?

I currently have the minimum API level set to 9 as this is the current LibGDX default but I'm willing to set this higher if it would be too difficult to target such a low API level.

Edit 1:

I just extracted the jar file produced by the core module and examined the class files using the javap tool.

I ran the following command on a random class file

java -verbose foo.class

and it output text with the following text

... minor version: 0 major version: 50 ...

using this question List of Java class file format major version numbers? I determined that the class file is actually targeting JVM 1.6.

Therefore my original theory is wrong and there is another reason why older Android versions cannot load classes generated by Kotlin lambdas.

Upvotes: 1

Views: 643

Answers (1)

Ryba
Ryba

Reputation: 1299

It looks like you are using functionality that only exists within the JDK 8 library. Specifically the computeIfAbsent() method on the Map class.

Because of this, even though your code has been compiled down to JVM 1.6 compatibility, the underlying implementation on Android devices is missing that functionality and thus the reason for the NoClassDefFoundError exception you were seeing.

Updated: You can see in the javadoc located at https://docs.oracle.com/javase/8/docs/api/java/util/Map.html#computeIfAbsent-K-java.util.function.Function- that the computeIfAbsent() has only been around since JDK 8

Upvotes: 1

Related Questions