ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.

Follow publication

Current issues with Kotlin Multiplatform and how to fix them

--

Recently I started to play around with Kotlin Multiplatform, which is still in an experimental state. Before I could start to do something productive I ran into several issues and wanted to inform about how to fix them, in case you also encounter them.

Setting the Android SDK path

The first issue you will automatically encounter after creating the project is:

Please fix the ‘sdk.dir’ property in the local.properties file

Just head over to Android Studio and open your local.properties and copy the sdk.dir value from there to the corresponding file in the Multiplatform project. You can now continue to build your project.

Renaming the Android source set

The project is created with a few source sets commonMain,commonTest, iosMain etc. However, the Android packages are named main and test which I found very confusing — why not androidMain? Sadly, a simple renaming of the package will lead to build errors.

Additionally, I opened the app/build.gradle and put this into the android {} block:

sourceSets {
main {
manifest.srcFile 'src/androidMain/AndroidManifest.xml'
java.srcDirs = ['src/androidMain/java']
res.srcDirs = ['src/androidMain/res']
}
}

telling Gradle where it would find the Android code instead.

Writing tests for classes in the common module

It’s nice to be able to write code that is getting shared between Android and iOS. But I need to test this code, too. Like I’m used from IntelliJ I pressed ⌘+⇧+T inside a class to create a test for this class.

I read that I need to use kotlin-test—it’s already added to the classpath, but there is no option to create a “Kotlin test” and all other testing frameworks suggested to use are wrong. Well, I just chose one and pressed continue. Luckily, you can simply start typing @Test and IntelliJ will ask you to import the correct library kotlin.test.Test. But then I noticed it created my test in the test package, which is the package for Android tests, instead of the commonTest package.

Lesson learned: Create all your common tests manually in the correct package right now.

Executing tests

After I finally wrote a test of course I wanted to execute it — but this would be too simple if it just worked!

In my case I added the kotlinx-serialization library and when trying to run a test for the common module my build failed with

Task :app:compileDebugAndroidTestKotlinAndroid FAILED
e: /mpp-test/app/src/commonTest/kotlin/sample/SampleTests.kt: (3, 16): Unresolved reference: serialization

I quickly noticed it failed when compiling Android test resources, but I just wanted to run common code, it has nothing to do with Android 🤷‍♂

After some searching I found this is currently a bug targeted to be resolved with Kotlin 1.3.40. For the time you explicitly need to add all Multiplatform dependencies meant for androidMain to androidTest, too.

After I fixed that I ran into another problem: Right clicking on commonTest and selecting “Run all tests” gave me:

No tasks available

I was like “well ok, then let’s run the test by right clicking on the test class and select “Run” from here”.

This worked, however, it ended with:

Test events were not received

Looking at the console output it tried to run Exeuting task ‘-tests “sample.sampleTests"'. It tried to execute… what?

Again, after looking through open Multiplatform bugs, I found the solution:

Go to Preferences | Build, Execution, Deployment | Build Tools | Gradle | Runner, Run tests using and selectPlatform Test Runner instead of Gradle Test Runner — you can now run your tests.

Gradle wrapper is missing

Tried to run anything from command line with Gradle? Currently no Gradle wrapper is added to new projects. For me I went to another project and copied gradlew, gradlew.bat and the directory gradle/wrapper to the Multiplatform project. Afterwards you can use ./gradlew build and stuff like that.

Unable to run app with Ktor as a dependency

If you want to do some networking then you will want to use Ktor. I added all dependencies for Multiplatform and tried to run my app. It failed with

FAILURE: Build failed with an exception.* What went wrong:
Execution failed for task ‘:app:transformResourcesWithMergeJavaResForDebug’.
> More than one file was found with OS independent path ‘META-INF/ktor-http.kotlin_module’

Add this to your android {} block:

packagingOptions {
exclude 'META-INF/*.kotlin_module'
}

Using Ktor with Kotlinx.serialization on Kotlin/Native

So, you can add a JsonFeature to Ktor, which enables Ktor to automatically parse some JSON retrieved from an API call to some entity:

install(JsonFeature) { serializer = KotlinxSerializer() }

If you now make a HTTP call with Ktor and define the return type of a function to be your entity, Ktor will do the rest for you by itself:

suspend fun doHttpCall(): YourEntity {        
return client.get { ... }
}

This worked fine when I tested it with Android. When implementing the iOS part, using Kotlin/Native, it threw an exception:

Obtaining serializer from KClass is not available on native due to the lack of reflection. “ + “Use .serializer() directly on serializable class.

This is currently a TODO in Kotlin/Native. So, just alter your method to something like this:

val response = client.get<HttpResponse> { ... }
val jsonBody = response.readText()
return Json.parse(YourEntity.serializer(), jsonBody)

Be careful with singletons

Kotlin/Native’s concurrency model is designed so it freezes object graphs to guarantee immutability. You can read more about that here.

At the end it states under which circumstances objects are automatically frozen. Definitely I should’ve read that beforehand.

I created a Kotlin object named ServiceLocator which should serve me as a very simple dependency injection tool. I put my Ktor HTTP client inside and then got an exception:

kotlin.native.concurrent.InvalidMutabilityException: mutation attempt of frozen io.ktor.client.request.HttpRequestPipeline

which was caused by exactly this issue. However, it took me a long time to figure that out.

You need to add @ThreadLocal to your object or move it out of the singleton. Adding @ThreadLocal however may have unwanted side effects, so be careful with using that and don’t see it as the ultimate solution.

That were some issue that took me some time to find and resolve 😉 Happy Multiplatform coding.

--

--

Published in ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.

Responses (8)

Write a response