So in the last “episode” we found that we could use different implementations of a class and function based on which target we are running - android, ios, desktop, (also javascript, native, etc). So we tried to use this for a resource loader and on desktop and android which use a jvm, we simply tried to use it the way we would have in a normal java/ kotlin project:
actual class ResourceLoader {
actual fun readTextFromFile(path: String): String? {
return this.javaClass.getResource(path)?.readText()
}
}
with our resources set up like this:
and this did not work. If we try to put another resources root in the desktop area, and include the test.txt there, it fails because of duplicate resources. So it feels like they should be in the resources. Lets look in our build directory:
there our test.txt
is, under processedResources/desktop/test/
So what is going on here?
If I place the test.txt
in the commonMain/resources/ directory,
the tests pass:
So clearly there is an issue with where test resources are placed.
Placing it in the desktopMain/resources
directory seems to work for that test but not the android tests.
Ok, this is starting to make sense now. So it seems commonTest/resources
is not included in the classpath for any of these targets. Seems we need to configure it somehow in our build.gradle.kts
sourceSets {
...
val desktopTest by getting{
resources.srcDir(commonTest.resources.srcDirs)
...
}
Low and behold it works! Yay! Of course in the androidUnitTest
the test still fails so we copy the fix to the androidUnitTest
:
sourceSets {
...
val androidUnitTest by getting {
resources.srcDir(commonTest.resources.srcDirs)
}
and run the androidUnitTest
version:
failed. Boo! Ok, what might be happening here? Once again, lets try putting it in the main resources for that platform: androidMain/resources
. Yup, that worked. Ok, step at a time, lets try the androidUnitTest/resources
. Yes, this works too. So something is wrong with how we are adding the commonTest.resources.srcDirs
to the androidUnitTest
resources sourceSet. Looking at the android build target in the build.gradle.kts
, it starts with some main sourceSet stuff:
android {
...
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
sourceSets["main"].res.srcDirs("src/androidMain/res")
sourceSets["main"].resources.srcDirs("src/commonMain/resources")
so lets add a test one to this and see if it works:
android {
...
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
sourceSets["main"].res.srcDirs("src/androidMain/res")
sourceSets["main"].resources.srcDirs("src/commonMain/resources")
sourceSets["test"].resources.srcDirs("src/commonTest/resources")
Sucess! With that there did we need the line in the sourceSet for the androidUnitTest? Removing it it still works. One more thing to test - run the androidInstrumentedTests. Arg! They failed! Frustrating. I can make it pass putting the file in the androidInstrumentedTest/resources/
directory, but it isn’t working from the commonTest/resources
. What does this mean? It must mean we haven’t modified the right sourceSet for androidInstrumentedTest
. A bit more digging and we find that there is also an androidTest
sourceSet for androidInstrumentedTest
:
android {
...
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
sourceSets["main"].res.srcDirs("src/androidMain/res")
sourceSets["main"].resources.srcDirs("src/commonMain/resources")
sourceSets["test"].resources.srcDirs("src/commonTest/resources")
sourceSets["androidTest"].resources.srcDirs("src/commonTest/resources")
now it runs! Obviously we will have to do something special for iOs as it compiles to native iOs code, so won’t have a classLoader to use getResources()
on. It will of course need its own ResourceLoader().readTextFile(path: String)
but as I don’t currently have access to a Mac I won’t be working on that until later.
So to summarize, here is the build.gradle.kts
which allows us to access the resources from commonTest
in desktopTest
, androidUnitTest
, and androidInstrumentedTest
:
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.androidApplication)
alias(libs.plugins.jetbrainsCompose)
}
kotlin {
androidTarget {
compilations.all {
kotlinOptions {
jvmTarget = "11"
}
}
}
jvm("desktop")
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach { iosTarget ->
iosTarget.binaries.framework {
baseName = "ComposeApp"
isStatic = true
}
}
sourceSets {
val desktopMain by getting
androidMain.dependencies {
implementation(libs.compose.ui.tooling.preview)
implementation(libs.androidx.activity.compose)
}
commonMain.dependencies {
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material)
implementation(compose.ui)
implementation(compose.components.resources)
implementation(compose.components.uiToolingPreview)
}
val commonTest by getting {
dependencies {
implementation(kotlin("test"))
implementation(libs.kotest.assertions.core)
}
}
val desktopTest by getting{
resources.srcDir(commonTest.resources.srcDirs)
dependencies {
implementation(compose.desktop.currentOs)
}
}
val androidInstrumentedTest by getting {
dependencies {
implementation(kotlin("test"))
implementation(libs.androidx.test.junit)
implementation(libs.androidx.test.runner)
implementation(libs.kotest.assertions.core)
}
}
}
}
android {
namespace = "com.ronnev.damon"
compileSdk = libs.versions.android.compileSdk.get().toInt()
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
sourceSets["main"].res.srcDirs("src/androidMain/res")
sourceSets["main"].resources.srcDirs("src/commonMain/resources")
sourceSets["test"].resources.srcDirs("src/commonTest/resources")
sourceSets["androidTest"].resources.srcDirs("src/commonTest/resources")
defaultConfig {
applicationId = "com.ronnev.damon"
minSdk = libs.versions.android.minSdk.get().toInt()
targetSdk = libs.versions.android.targetSdk.get().toInt()
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
dependencies {
debugImplementation(libs.compose.ui.tooling)
}
}
compose.desktop {
application {
mainClass = "MainKt"
nativeDistributions {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
packageName = "com.ronnev.damon"
packageVersion = "1.0.0"
}
}
}
You can see the whole project as it stands here. I am not satisfied with one aspect of this: my test is copied in androidInstrumentedTest, androidUnitTest and desktopTest. If I try to move it to commonTest, it gives me errors but it does run, but they fail. Still we can function like this!
Happy Testing!