https://kotlinlang.org logo
#multiplatform
Title
# multiplatform
s

Sebastian Sellmair [JB]

08/24/2022, 11:14 AM
Hey Folks 👋 I was once convinced that sharing code between android + jvm (https://youtrack.jetbrains.com/issue/KT-42466/HMPP-JVM-Android-intermediate-source-set) is underrated. I wanted to write an internal document, making a case for it and trying to raise awareness. However, I currently fail to compile good enough arguments. So: If there is anybody who also thinks this is important, would you mind sharing your thoughts? (You’re also invited if you think this case is not important)
h

hfhbd

08/24/2022, 11:15 AM
JetBrains Compose for Android and Desktop? I guess, this is a very good use case.
s

Sebastian Sellmair [JB]

08/24/2022, 11:25 AM
But is it? The compose team claims, that they do not even have any public API in their ‘jvmMain’ source set. Couldn’t you do such a project setup by using one android and one desktop Gradle project instead of using a shared SourceSet?
g

Grégory Lureau

08/24/2022, 12:05 PM
Personally I use a dedicated desktop project as you said, but on a little project. I got issues trying to define
val androidMain by getting { dependsOn(jvmMain) }
due to stuff like Date being not compatible. This kind of differences means that you eventually ends up filling jvmMain/androidMain (or else you use a library that does it for you, so same). I'd be very happy to share my compose code (I understood it's not feasible yet).
j

Javier

08/24/2022, 12:05 PM
@Sebastian Sellmair [JB] I copied the workaround of setting the same dir (
jvmAndAndroidMain
) from compose-jb repo. TBH that is a hack to a missing feature (well the feature exists but it doesn't work with this combination) and it is used in a lot of projects, including jetbrains ones
h

hfhbd

08/24/2022, 12:05 PM
You can use two gradle module unless you are using expect/actual. And especially for shared UI I would put all my desktop and android code into one module and use expect/actual for device specific implementations. For example: in androidJvm I have the shared UI, eg lists/buttons and with expect/actual some specifc things like a date/time picker. On android you will get a dialog sometimes with a clock designed picker, on desktop you can use the keyboard.
g

Grégory Lureau

08/24/2022, 12:07 PM
It depends so much of the project actually 🙂 Being able to do it will definitely offer the possibility of more architecture choices.
j

Javier

08/24/2022, 12:07 PM
If I was a new user of KMP, having a totally different approach for jvm + android which I have to find on whatever repo of github is not a good experience
g

Grégory Lureau

08/24/2022, 12:12 PM
(splitting or not on github repositories is not related to this problematic from my point of view 🤔)
j

Javier

08/24/2022, 12:13 PM
not sure what you mean @Grégory Lureau
if your reply is about my last comment, I mean that the workaround is not on the doc (I think) and I had to go to compose-jb or other kmp projects to find and copy it in my projects
if I was a new user, that can be annoying
g

Grégory Lureau

08/24/2022, 12:57 PM
(sorry I misunderstood your message, it's clear now and I agree on the documentation split that is not obvious)
j

Javier

08/24/2022, 12:59 PM
Don't worry, probably my words weren't the best too 😛
e

eygraber

08/24/2022, 4:55 PM
Regarding Compose and Android + JVM: I have a module that I want to have Compose extensions for (without having to make it a separate module/artifact), and I'd like those Compose extensions to be available to Android and JVM. From what I understand, today I'd have to have separate
androidMain
and
jvmMain
source sets with the source code in both being exactly the same. I can't use
commonMain
because this module has other targets and I don't want Compose code in them.
m

Michael Paus

08/24/2022, 6:19 PM
For me, the lack of this feature is so obvious that I wonder what arguments actually speak against its implementation other than the fact that it may be complicated and cause work.
j

Jeff Lockhart

08/24/2022, 9:42 PM
I have a use case, where I've written KMP bindings for a database. Currently it works for Android and iOS, using the database's Android and ObjC SDKs via expect/actual. The database also has a JVM SDK, itself sharing most of the code that interfaces the underlying native core with Android. Only one API differs, where the Android SDK is initialized with a Context. Admittedly, I haven't spent much time looking into it yet because I've seen shared JVM/Android source sets aren't supported. But if they were, I would expect it would be very easy to move most of my code to the shared source set, add the Java library as a JVM dependency, and be able to quickly add support for another KMP target.
g

Grégory Lureau

08/25/2022, 7:12 AM
(It will build but I'd be afraid to use a JVM dependency on Android if I need to store
Date
in the database for example, I've got runtime exceptions doing that, because JVM methods are not all available on Android.)
m

Michael Paus

08/25/2022, 9:01 AM
I don’t understand why people are so shy about using JVM dependencies in Android. My project is full of such dependencies and they all work like a charm. Here is just a fraction of the ones that I use:
Copy code
commons-csv
commons-io
commons-lang3
commons-math3
earth-gravitational-model
GeographicLib-Java
indriya
jpx
jts-core
jts-io-common
org.eclipse.paho.client.mqttv3
si-quantity
si-units
unit-api
uom-lib-common
Modern Android has become very compatible with the JVM and I have seldom seen any problems. I even use Java 11 for all my libs without problem. Of course you have to prepare for it and do some testing. I only had one major problem with sqlite-jdbc which doesn’t work on android but that could be easily replaced by sqldroid, so that the rest of the code, which uses JDBC, could stay untouched. Even the Eclipse Paho MQTT-client worked without problems out of the box. So you see that I have a large model which can be fully shared between JVM and Android and I certainly do not want to duplicate that in any way nor tear it apart for no good reason.
g

Grégory Lureau

08/25/2022, 9:16 AM
I'm shy because it builds and crash at runtime, I don't like that because it depends of the runtime compared to what I'm used to. (but I agree if it works for your use cases, there's no problem to use it indeed)
m

Michael Paus

08/25/2022, 9:22 AM
To avoid misunderstandings you might add which
Date
class you mean and which database. If you mean
java.util.Date
then this should not be used anymore anyway. The java.time API is much better but of course I don’t know why you are using
java.util.Date
. (https://developer.android.com/reference/java/time/package-summary) The JVM interoperability also depends on your build configuration. I am using:
Copy code
val JVM = JavaVersion.VERSION_11

android {
    compileSdk = 32
    sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
    defaultConfig {
        minSdk = 26
        targetSdk = 32
    }
    compileOptions {
        sourceCompatibility = JVM
        targetCompatibility = JVM
    }
    namespace = ...
    tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
        kotlinOptions {
            jvmTarget = JVM.toString()
        }
    }
}
j

Jeff Lockhart

08/25/2022, 2:37 PM
I've adapted my database API in common to use
kotlinx.datetime.Instant
instead of
java.util.Date
, which the underlying Android and Java SDKs both use. So users won't be manipulating
Date
objects with Java or Android's supported subset of APIs. This is necessary for a common date API to replace ObjC's
NSDate
.
actual
platform implementations each convert `Instant`s to and from their native type when interacting with the underlying database APIs.
This got me curious, so I spent some time adding the jvm target to my database bindings. Sure enough, it was relatively simple to move all of my androidMain code to jvmCommonMain, except for the one API that differs. Also, most androidAndroidTest code could be used with a jvmCommonTest shared source set, with only a few platform-dependent `actual`s, like reading test resources. I tested both of these mechanisms for a shared Android/JVM source set. As described, the second mechanism, adding jvmCommon* as a
srcDir
for both android* and jvm*, works best. jvmCommon* code sees the Java platform APIs and jvm dependencies in the IDE. The one annoyance is that common* code doesn't detect the jvm `actual`s for `expect`s. So the IDE shows
Expected class '<ClassName>' has no actual declaration in module <module-name>.main for JVM
throughout the common code. This is not an issue with the first mechanism, which would be the expected way to implement a shared source set. But not seeing platform and dependency APIs is worse than having to
@Suppress("NO_ACTUAL_FOR_EXPECT")
for every
expect
(which exist in nearly every common file of my library 😣) and deal with missed actuals at compile time. Both mechanisms technically work, all tests passing on both android and jvm. It's just IDE support that's lacking. This is a use case that is not solvable using separate modules. The article mentions this library as another example.
14 Views