Retrolambda/Multimodule confusion - android

I am using retrolambda and some tests got me quite confused.
My understanding is:
with retrolambda I can use some java8 language features (e.g. lambdas), but not java 8 classes: e.g. without any additional libraries, I cannot use the java8 streams: HashSet<>.stream() is not available
I write my code with java-8 features (lambdas). The source code is translated into java8-class files and then retrolambda kicks in and converts the java8-class files to java7-class files which can be executed on older Android version (e.g. Android 6)
So, I've built a small test-project to test my assumptions and there are some things I am not sure of. I hope someone can clarify.
Heres a tag of my source code, that I will refer to.
in a multimodule android application (one android module and a java-only module), we must apply the retrolambda gradle plugin to all modules (app and java) that want to use java8 features
first the java module will be compiled by javac to java8-class files, retrolambda will convert them to java7 and a jar is built
then the app module will be compiled the same way as the java module: but now we get the apk instead of a jar: everything is java7 and can happily be used on Android 6
In my source code I have some unit tests, that deliberately use java8 code that should not work: I call HashSet<>.stream()
Android studio and the Emulator: All tests work
I guess that Android Studio & Emulators do not even use retrolambda at all and just compile and run everything to java8
Is there a way to change that?: e.g. it would be good if any of the tests already catch these problems
Gradle command line build:
gradle clean app:test: The unit tests work
gradle clean java:test: The unit tests fail
I have no idea why the tests in the app module work and those in the java module fail: why is that?
On a real device with Android 6 everything crashes, as expected

Related

Running multi platform Kotlin test in IntelliJ produces No JDK specified error

I have several tests in common module for multi platform Kotlin project. When I execute those tests using gradle, e.g. ./gradlew :android:test, they all go through and the tests run.
I have now encountered a more complicated problem where I would like to debug an actual test in IntelliJ. Unfortunately, upon selecting the debug option in IntelliJ, I get an No JDK specified error.
I am using the following dependencies for testing:
testImplementation "org.jetbrains.kotlin:kotlin-test-annotations-common:$kotlin_version"
testImplementation "org.jetbrains.kotlin:kotlin-test-common:$kotlin_version"
with $kotlin_version being 1.2.41.
The common module settings looks like this:
SDKs section also correctly recognises JDKs:
I have tried changing the Module SDK from Kotlin SDK to java, however IntelliJ then wants me to require jUnit for test execution, which I would prefer not to, if possible.
Is there a way how to make the debugger run in IntelliJ for the Kotlin code?
Found the solution.
Just like it does not make sense to execute tests using Gradle in the common module alone, e.g. ./gradlew :common:test and the tests need to be executed for a specific platform ./gradlew :android:test, because the common module might contain expected declarations which are supposed to be implemented per platform using the actual keyword, it also does not make sense to debug in the common module directly.
Instead, for such purposes, the test to be debugged must be placed in a specific platform module, for my purpose I have chosen the Android module, and then it can be executed and debugged.
As I have mentioned, this approach is necessary because in the Android module the expected structures are actually replaced by the actual implementations.

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.
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

Android NDK Debugging in an included library module

I am currently working on an Android app that is using a library that is using the NDK. The library and the app are maintained in separate repositories and are worked on as separate projects. The library is compiled on its own with Gradle and the ndk-build script. The aar output and .so files are then included in the main application.
My question is:
Is there a way to use ndk-gdb (or some alternative) to debug the library while running in the main app?
I have the source code for both projects, and if need be I could combine the projects for use with ndk-gdb, but this is very messy and requires a lot of additional work that would not be version controlled.

Android Junit test - Mockito class not found exception

I'm attempting to build a unit test on Android which utilizes the mockito-all-1.8.5.jar file from mockito.org. I added the jar file to my project using project > properties > project build path > add external jar. This has always worked for me on non-Android java projects.
However, when I run the unit test, I get the following class not found exception:
java.lang.ClassNotFoundException: org.mockito.runners.VerboseMockitoJUnitRunner
Above it is the following message:
Cannot load class. Make sure it is in your apk. Class name: 'org.mockito.runners.VerboseMockitoJUnitRunner'. Message: org.mockito.runners.VerboseMockitoJUnitRunner
I checked the jar and the class is there.
I also tried adding according to this link:
How to use and package a JAR file with my Android app?
But, still no luck. How can I get this class to be found?
Edit: I'm thinking there is a problem with whatever libraries Mockitto depends on - they may not be compatible with the Dalvik jvm. See this post:
http://daverog.wordpress.com/2009/12/14/why-android-isnt-ready-for-tdd-and-how-i-tried-anyway/
1) Import a mocking framework (e.g. mockito) into the project as an additional dependency.
Any imported jars containing class files not compiled to Dalvik bytecode (most) will not work. Attempting to compile the source along with your project will not work either because most libraries will make extensive use of parts of the Java language not compatible with Dalvik: it uses its own library built on a subset of the Apache Harmony Java implementation.
If you are executing inside the Dalvik JVM you won't be able to use Mockito.
If you are running your unit tests on your desktop JVM however (with a testing framework like Robolectric for example) then you are good to go. I am combining the two on my Android projects at present, to good effect.

Self-contained test library project cannot find the library classes

According to this SDK guide, unit-testing a Library project can be achieved by creating a standard application project, reference the Library project and then instrument the application for unit testing.
However, when I do this and launch the test application I get the message
No tests found with test runner 'JUnit 3'.
I'm using Eclipse and the Android ADT plugin, all latest versions.
Note: the projects compile just fine. The test project also installs fine to the emulator. But in the console I can see that it looks for <library>.apk, which of course doesn't exist since I'm compiling this as a library into the test project.
Anyone got this to work? And if so, what is the trickery here?
Update: after discovering and fixing a problem, which was actually including the test classes (!), the test runner now can find all tests. But, all the tests fail with the following exceptions:
java.lang.NoClassDefFoundError: <nameOfClassInLibraryProject>
nameOfClassInLibraryProject are classes defined in the library project. These classes should be compiled into the test project, and indeed, everything compiles just fine. But when running the test project, the runtime doesn't seem to find the library classes.
After much fiddling and wasted time in Eclipse I have managed to get Android Library projects to work.
According to the Working with Library Projects article:
Instead, you must compile the library indirectly, by referencing the library from a dependent application's build path, then building that application.
The problem was that I interpreted this to mean that the library project should be added to the Projects tab in Java Build Path. Doing this makes the test project compile since the library code is obviously available to the compiler. But since the library is not compiled into a .jar or .apk in itself, the library classes are never deployed to the device.
The solution is to not add the library project to Projects, rather on the Source tab, add the library /src folder using the Link Source... button. And yes, it is the library src folder, not the library project root, that must be linked into the test project.

Categories

Resources