Kotlin MPP - Cannot resolve Stetho Interceptor - kotlin

I'm trying out for the first time to build a Kotlin MPP using a Kotlin DSL gradle file.
The issue is quite simple but I've been trying everything I could think of to fix it : the android shared code can't resolve Stetho interceptor
Screenshot of the issue
My build.gradle.kts
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
plugins {
kotlin("multiplatform")
id("kotlinx-serialization")
}
kotlin {
//select iOS target platform depending on the Xcode environment variables
val iOSTarget: (String, KotlinNativeTarget.() -> Unit) -> KotlinNativeTarget =
if (System.getenv("SDK_NAME")?.startsWith("iphoneos") == true)
::iosArm64
else
::iosX64
iOSTarget("ios") {
binaries {
framework {
baseName = "SharedCode"
}
}
}
jvm("android")
sourceSets["commonMain"].dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-common")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:1.3.3")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime-common:0.14.0")
// HTTP
implementation("io.ktor:ktor-client-core:1.3.0-rc")
implementation("io.ktor:ktor-client-json:1.3.0-rc")
implementation("io.ktor:ktor-client-serialization:1.3.0-rc")
}
sourceSets["androidMain"].dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3")
implementation("io.ktor:ktor-client-android:1.3.0-rc")
implementation("io.ktor:ktor-client-json-jvm:1.3.0-rc")
implementation("io.ktor:ktor-client-serialization-jvm:1.3.0-rc")
implementation("io.ktor:ktor-client-okhttp:1.3.0-rc")
implementation("com.squareup.okhttp3:logging-interceptor:4.0.1")
implementation( "com.facebook.stetho:stetho-okhttp3:1.5.1")
}
Thanks a lot for your help !
EDIT :
So I found out that when I replace
jvm("android")
with
android()
Stetho-interceptor can be imported, but the "expected" and "actual" keyword are not correctly linked by Android studio for iOS :
"Actual property ... has no corresponding expected declaration" when I hover an actual declaration for iOS
When I use both
jvm("android")
android()
I get a gradle error :
"The target 'android' already exists, but it was not created with the 'jvm' preset. To configure it, access it by name in kotlin.targets or use the preset function 'android'."

you do not need to have jvm("android") if you already are using the android() target. if you want a separate jvm target then you should name it something different (ex. jvm("something") ).
Once you add your android target and manifest + android definitions, the libraries should resolve. the facebook library likely depends on an android target which is why it is not resolving.

Related

Unable to resolve cinterop IOS import in Kotlin Multiplatform

I have followed the Kotlin documentation for adding iOS dependencies. In my case the dependency is a pre-compiled framework provided through a third party. So I have followed the case for framework without cocoapod.
I placed my MyFramework.def file in /src
language = Objective-C
modules = MyFramework
package = MyFramework
Then I added the following to the build.gradle.kts in the Kotlin object
```
ios {
binaries {
framework {
baseName = "shared"
}
}
}
iosArm64() {
compilations.getByName("main") {
val JWBLe by cinterops.creating {
// Path to .def file
defFile("src/nativeInterop/cinterop/MyFramework.def")
compilerOpts("-framework", "MyFramework", "-F/Users/user/Projects/MyFramework/ios/SDK")
}
}
binaries.all {
// Tell the linker where the framework is located.
linkerOpts("-framework", "MyFramework", "-F/Users/user/Projects/MyFramework/ios/SDK")
}
}
sourceSets {
val commonMain by getting
val commonTest by getting {
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
}
}
val androidMain by getting {
dependencies {
implementation("com.google.android.material:material:1.2.1")
}
}
val androidTest by getting {
dependencies {
implementation(kotlin("test-junit"))
implementation("junit:junit:4.13")
}
}
val iosMain by getting
val iosTest by getting
}
Then I build the project. The library does indeed get seen and I see that in External Libraries, there is a shared-cinterop-MyFramework.klib
However, when I try to import this package into my code under src/iosMain/kotlin/com.example.testapp.shared/platform.kt
I get unresolved error for the library. It seems like I should also need to add something to sourceSets? But I am unsure.
First of all, I got to notice that the Gradle script is incorrect. In this case, the iosArm64 target was declared twice - by the target shortcut and once again where you configure the cinterop. To avoid this duplication, it would be better to configure cinterop like that:
ios()
val iosArm = targets.getByName("iosArm64") as org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
// A bit dirty cast, but as I'm sure iosArm64 is the Native target, it should be fine. Needed to make highlighting below work as expected.
iosArm.apply {
compilations.getByName("main") {
val JWBLe by cinterops.creating {
// Path to .def file
defFile("src/nativeInterop/cinterop/MyFramework.def")
compilerOpts("-framework", "MyFramework", "-F/Users/user/Projects/MyFramework/ios/SDK")
}
}
binaries.all {
// Tell the linker where the framework is located.
linkerOpts("-framework", "MyFramework", "-F/Users/user/Projects/MyFramework/ios/SDK")
}
}
However, this adjustment won't help with accessing cinterop bindings from the iosMain. In the current state of commonizer, it can share only platform libraries. So anyway, moving all code utilizing those bindings into the src/iosArm64Main folder is the best option available at the moment. Here go an issue from the official tracker to upvote and subscribe - Support commonization of user-defined libraries.
So after some playing around I found the answer.
The dependency was set for a module of iosArm64 which is not available to the iosMain.
I created another folder src/iosArm64Main and placed the source file there. At that point it was able to resolve the library.

Setting up gradle and project structure for Kotlin Multiplatform project

I want to build a CLI tool with Kotlin Multiplatform which runs on Linux, Macos and Windows.
But I am struggling with setting up my build.gradle and my project structure. I am using IntelliJ IDEA 2020.1 and created my basic project with File -> New -> Project -> Kotlin / Native | Gradle
Currently I am looking through guides from kotlinlang.org but I am more falling then achieving something.
So far my build.gradle looks as follows:
plugins {
id 'org.jetbrains.kotlin.multiplatform' version '1.3.72'
}
repositories {
mavenCentral()
}
kotlin {
// For ARM, should be changed to iosArm32 or iosArm64
// For Linux, should be changed to e.g. linuxX64
// For MacOS, should be changed to e.g. macosX64
// For Windows, should be changed to e.g. mingwX64
linuxX64("linux") {
}
mingwX64("mingw") {
}
macosX64("macos") {
binaries {
executable {
// Change to specify fully qualified name of your application's entry point:
entryPoint = 'sample.main'
// Specify command-line arguments, if necessary:
runTask?.args('')
}
}
}
sourceSets {
commonMain {
kotlin.srcDir('src/main')
resources.srcDir('src/res')
dependencies {
implementation kotlin('stdlib-common')
implementation "com.github.ajalt:clikt-multiplatform:2.7.0"
}
}
commonTest {
dependencies {
implementation kotlin('test-common')
implementation kotlin('test-annotations-common')
}
}
macosX64().compilations.test.defaultSourceSet {
dependsOn commonMain
}
// Note: To enable common source sets please comment out
'kotlin.import.noCommonSourceSets' property
// in gradle.properties file and re-import your project in IDE.
macosMain {
}
macosTest {
}
}
}
wrapper {
gradleVersion = "6.4.1"
distributionType = "ALL"
}
And my project structure is still basic:
Project structure
Formerly I only worked on Android Projects with Kotlin, and I guess I am spoiled with gradle as Android generates the most basic stuff and everything is working without doing that much.
I understand that I need to create packages like linuxMain and mingwMain, but where to I put common sourcesets? I tried to create a package called commonMain, but it won't even let me create Kotlin files in that package.
When I am finished I want to have (in the best case) one common source set and one entry point for all my targets. Is this even possible?
As far as I can see, you specify your commonMain source set's source locations as /src/main/. By default, it's usually set onto /src/commonMain/kotlin/. So if you will remove those srcDir settings and create a .kt file in your /src/commonMain/kotlin/ folder, everything should work fine. Also, I hope you have removed 'kotlin.import.noCommonSourceSets' property from your gradle.properties as your script recommended.

Runing coroutines in IntelliJ Kotlinx error

I'm trying to run a native Kotlin project using coroutines using IntelliJ IDEA Community 2020.
Here is how my build.gradle looks:
plugins {
id 'org.jetbrains.kotlin.multiplatform' version '1.3.72'
}
repositories {
mavenCentral()
}
kotlin {
// For ARM, should be changed to iosArm32 or iosArm64
// For Linux, should be changed to e.g. linuxX64
// For MacOS, should be changed to e.g. macosX64
// For Windows, should be changed to e.g. mingwX64
mingwX64("mingw") {
binaries {
executable {
// Change to specify fully qualified name of your application's entry point:
entryPoint = 'sample.main'
// Specify command-line arguments, if necessary:
runTask?.args('')
}
}
}
sourceSets {
// Note: To enable common source sets please comment out 'kotlin.import.noCommonSourceSets' property
// in gradle.properties file and re-import your project in IDE.
mingwMain {
}
mingwTest {
}
}
}
// Use the following Gradle tasks to run your application:
// :runReleaseExecutableMingw - without debug symbols
// :runDebugExecutableMingw - with debug symbols
And here is a simple KT file:
package sample
import kotlinx.coroutines.*
fun main() = runBlocking<Unit> {
val deferred = async(Dispatchers.Unconfined, CoroutineStart.LAZY) {
println("Running Async Unconfined: on thread ${Thread.currentThread().name} has run.")
42
}
val result = deferred.await()
println("Async Unconfined Result is ${result}")
}
I installed the maven plugin under Project Structure | Module and screenshot attached.
Nevertheless, I'm getting "Unresolved References..." error. Attached screenshot...
Request if someone can help me to resolve this please?
Thanks
In your Project Structure > Modules > Dependencies tab, you selected Runtime as the scope, which makes the dependency only available at runtime (usually used for transitive dependencies). Try selecting Compile here.

Create fat jar from kotlin multiplatform project

I recently switched from old 1.2 multiplatform into 1.3. Difference is, there's one one build.gradle file per multiplatform module (I got 5 of them) so a lot less configuration.
However I can't seem to be able to configure creating runnable fat jar with all dependencies from jvm platform.
I used to use standard "application" plugin in my jvm project and jar task, but that does not work anymore. I found there's "jvmJar" task and I modified it (set Main-class), but created jar doesn't contain dependencies and crashes on ClassNotFoundException. How do I do it?
This is what I have now:
jvm() {
jvmJar {
manifest {
attributes 'Main-Class': 'eu.xx.Runner'
}
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
}
}
I did hit that bump and used this work around.
1. Restructure your project
Lets call your project Project.
create another submodule say subA, which will have the gradle notation Project:subA
now, subA has your multiplatform code in it (It is the gradle project with apply :kotlin-multiplafrom) in its build.gradle
2. Add Another submodule
create another submodule which targets only jvm say subB, which will have the gradle notation Project:subB
So, subB will have plugins: 'application' and 'org.jetbrains.kotlin.jvm'
3. Add your module as a gradle dependency (see my build.gradle)
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.3.31'
id "application"
}
apply plugin: "kotlinx-serialization"
group 'tz.or.self'
version '0.0.0'
mainClassName = "com.example.MainKt"
sourceCompatibility = 1.8
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
dependencies {
implementation project(':subA')
}
you can proceed and build subB as you would a regular java project or even use the existing plugins, it will work
Got it working with the multiplatform plugin in kotlin 1.3.61:
The following works for a main file in src/jvmMain/kotlin/com/example/Hello.kt
Hello.kt must also specify its package as package com.example
I configured my jvm target in this way:
kotlin {
targets {
jvm()
configure([jvm]) {
withJava()
jvmJar {
manifest {
attributes 'Main-Class': 'com.example.HelloKt'
}
from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
}
}
}
}
Got it to work with a slightly modified version of what luca992 did:
kotlin {
jvm() {
withJava()
jvmJar {
manifest {
attributes 'Main-Class': 'sample.MainKt'
}
from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
}
}
...
}
The only way to get gradle/multiplatform working appears to be endless trial and error; It's a nightmare, it's not being built as a "build" system so much as a "build system"; to put it another way, these two tools (together or in isolation) are a means of implementing only a single software development life cycle that the plugin maker intended, however, if you've engineered a desired software lifecycle and CI/CD system and now your trying to implement that engineering, it will be MUCH harder to do it with these tools than it would be to do it with scripts, code or maven. There are a number of reasons for this:
Massive changing in coding convention due to the plugin makers only exposing bar minimum configurability, probably only giving access to the things they need for their own personal project.
Very poor documentation updates; Kotlin, gradle and plugins are changing so rapidly I have begun to seriously question the usefulness of these tools.
Thus, at the time of writing this seems to be the correct syntax to use when using kotlin 1.3.72, multiplatform 1.3.72, ktor 1.3.2 and gradle 6.2.2 (using the kts format).
Note the fatJar seems to assemble correctly but won't run, it can't find the class, so I included the second runLocally task I've been using in the mean time.
This isn't a complete solution so I hate posting it on here, but from what I can tell... it is the most complete and up to date solution I can find documented anywhere.
//Import variables from gradle.properties
val environment: String by project
val kotlinVersion: String by project
val ktorVersion: String by project
val kotlinExposedVersion: String by project
val mySqlConnectorVersion: String by project
val logbackVersion: String by project
val romeToolsVersion: String by project
val klaxonVersion: String by project
val kotlinLoggingVersion: String by project
val skrapeItVersion: String by project
val jsoupVersion: String by project
val devWebApiServer: String by project
val devWebApiServerVersion: String by project
//Build File Configuration
plugins {
java
kotlin("multiplatform") version "1.3.72"
}
group = "com.app"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
jcenter()
jcenter {
url = uri("https://kotlin.bintray.com/kotlin-js-wrappers")
}
maven {
url = uri("https://jitpack.io")
}
}
//Multiplatform Configuration
kotlin {
jvm {
compilations {
val main = getByName("main")
tasks {
register<Jar>("buildFatJar") {
group = "application"
manifest {
attributes["Implementation-Title"] = "Gradle Jar File Example"
attributes["Implementation-Version"] = archiveVersion
attributes["Main-Class"] = "com.app.BackendAppKt"
}
archiveBaseName.set("${project.name}-fat")
from(main.output.classesDirs, main.compileDependencyFiles)
with(jar.get() as CopySpec)
}
register<JavaExec>("runLocally") {
group = "application"
setMain("com.app.BackendAppKt")
classpath = main.output.classesDirs
classpath += main.compileDependencyFiles
}
}
}
}
js {
browser { EXCLUDED FOR LENGTH }
}
sourceSets { EXCLUDED FOR LENGTH }
}

How to add default `jvm` sourceSet to `android` target in Kotlin Multiplatform?

With Kotlin/Multiplatform 1.3.11 i had the following build.gradle:
fromPreset(presets.jvm, 'jvm')
fromPreset(presets.android, 'jvm') // reusing jvm sources for android platform impl
So basically i wanted to reuse all code for jvm target for android target.
With just-released Kotlin/Multiplatform 1.3.20 now i'm getting an error:
The target 'jvm' already exists, but it was not created with the 'android' preset. To configure it, access it by name in kotlin.targets or use the preset function 'jvm'
Open File
I've tried to migrate to new syntax:
jvm()
android() {
sourceSets.add(kotlin.targets.jvm.compilations.main.defaultSourceSet)
}
but it does not reuse jvm defaultSourceSet for Android target:
Expected class 'URL' has no actual declaration in module
so it does not use default jvm sourceset actually and not throwing Groovy syntax error.
What's wrong?
Actually, the best solution is to have some common sourceset and one for each platform:
commonJvmMain {
dependencies {
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
}
}
jvmMain {
dependsOn commonJvmMain
}
androidMain {
dependsOn commonJvmMain
}
Put shared code in commonJvmMain sourceset directory.
https://github.com/JetBrains/kotlin-native/issues/2577