Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ktor dependencies not resolved in ios module of multiplatform project

I have a kotlin-multiplatform project targeting iOS and Android. Ktor http client is used in common module.

Everything works great with Android app. But when building project with iOS lib, I receive following exceptions:

> Task :app:compileKotlinIos FAILED
src/commonMain/kotlin/com/ottamotta/mozoli/api/MozoliApiKtor.kt:4:8: error: unresolved reference: io
import io.ktor.client.HttpClient
       ^
src/commonMain/kotlin/com/ottamotta/mozoli/api/MozoliApiKtor.kt:5:8: error: unresolved reference: io
import io.ktor.client.features.feature

...and other ones, saying none of the ktor dependencies have been resolved.

Here is build.gradle:

plugins {
    id 'kotlin-multiplatform' version '1.3.10'
}
repositories {
    google()
    jcenter()
    mavenCentral()
    maven { url "https://kotlin.bintray.com/kotlinx" }
}

ext {
    support_lib_version = '28.0.0'
    ktor_version = '1.0.0'
}

def keystorePropertiesFile = rootProject.file("./app/secret.properties");
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android-extensions'

android {
    compileSdkVersion 28
    buildToolsVersion '28.0.3'
    defaultConfig {
        applicationId "com.ottamotta.mozoli"
        minSdkVersion 21
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        manifestPlaceholders = [auth0Domain: "@string/com_auth0_domain", auth0Scheme: "https"]
    }
    buildTypes {

        debug {
            resValue "string", "com_auth0_client_id", keystoreProperties['com_auth0_client_id']
        }
        release {
            resValue "string", "com_auth0_client_id", keystoreProperties['com_auth0_client_id']
            minifyEnabled false
        }
    }

    lintOptions {
        abortOnError false
    }


}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "com.android.support:recyclerview-v7:${support_lib_version}"
    implementation "com.android.support:appcompat-v7:${support_lib_version}"
    implementation 'com.squareup.picasso:picasso:2.71828'

    implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.9.7"

    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.1")
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.1'

    implementation "com.auth0.android:auth0:1.14.1"

    androidTestImplementation 'com.android.support.test:runner:1.0.2'
}

kotlin {
    targets {
        fromPreset(presets.android, 'android')
        // This preset is for iPhone emulator
        // Switch here to presets.iosArm64 (or iosArm32) to build library for iPhone device
        fromPreset(presets.iosX64, 'ios') {
            compilations.main.outputKinds('FRAMEWORK')
        }
    }
    sourceSets {
        commonMain {
            dependencies {
                implementation 'org.jetbrains.kotlin:kotlin-stdlib-common'
                implementation "io.ktor:ktor-client:$ktor_version"
                implementation "io.ktor:ktor-client-json:$ktor_version"
                implementation "io.ktor:ktor-client-jackson:$ktor_version"
            }
        }
        commonTest {
            dependencies {
                implementation 'org.jetbrains.kotlin:kotlin-test-common'
                implementation 'org.jetbrains.kotlin:kotlin-test-annotations-common'
            }
        }
        androidMain {
            dependencies {
                implementation 'org.jetbrains.kotlin:kotlin-stdlib'
                implementation "io.ktor:ktor-client-android:$ktor_version"
            }
        }
        androidTest {
            dependencies {
                implementation 'org.jetbrains.kotlin:kotlin-test'
                implementation 'org.jetbrains.kotlin:kotlin-test-junit'
            }
        }
        iosMain {
            dependencies {
                implementation("io.ktor:ktor-client-ios:$ktor_version")
            }
        }
        iosTest {
        }
    }
}

task copyFramework {
    def buildType = project.findProperty("kotlin.build.type") ?: "DEBUG"
    def target = project.findProperty("kotlin.target") ?: "ios"
    dependsOn "link${buildType.toLowerCase().capitalize()}Framework${target.capitalize()}"

    doLast {
        def srcFile = kotlin.targets."$target".compilations.main.getBinary("FRAMEWORK", buildType)
        def targetDir = getProperty("configuration.build.dir")
        copy {
            from srcFile.parent
            into targetDir
            include 'app.framework/**'
            include 'app.framework.dSYM'
        }
    }
}

Here is the code of the file from common module which generates errors:

package com.ottamotta.mozoli.api

import com.ottamotta.mozoli.*
import io.ktor.client.HttpClient
import io.ktor.client.features.feature
import io.ktor.client.features.json.JsonFeature
import io.ktor.client.features.json.JsonSerializer
import io.ktor.client.features.json.defaultSerializer
import io.ktor.client.request.header
import io.ktor.client.request.request
import io.ktor.client.request.url
import io.ktor.http.HttpMethod


class MozoliApiKtor(
    private val serverUrl: String,
    private var jsonSerializer: JsonSerializer? = null,
    private val tokenProvider: suspend () -> String?
) : MozoliApi {

    private val client: HttpClient

    private val AUTH_HEADER = "Authorization";
    private val TOKEN_PREFIX = "Bearer "

    init {
        client = HttpClient {
            install(JsonFeature) {
                serializer = jsonSerializer ?: defaultSerializer()
            }
        }
        jsonSerializer = client.feature(JsonFeature)?.serializer
    }

    override suspend fun getUserProfile(): User {
        return client.request {
            url("${serverUrl}/user/")
            method = HttpMethod.Get
            header(AUTH_HEADER, TOKEN_PREFIX + tokenProvider())
        }
    }

    override suspend infix fun getEventsByCity(cityId: String): List<Event> {
        return client.request {
                url("${serverUrl}/event/city/${cityId}")
                method = HttpMethod.Get
                header(AUTH_HEADER, TOKEN_PREFIX + tokenProvider())
            }

    }

}
like image 985
Rodion Altshuler Avatar asked Nov 27 '18 21:11

Rodion Altshuler


People also ask

Is KTOR multiplatform?

The Ktor HTTP client can be used on different platforms, including JVM, Android, JavaScript, and Native.

Is KTOR better than retrofit?

What's KTor Client and how is it different from Retrofit for Android? Ktor client is an HTTP client that can be used for making requests and handling responses. It works pretty similar to Retrofit but what makes it stand out is that it is not wired to anything android specific and is completely Kotlin powered.

How do I use KTOR?

To use Ktor, you first need to add the Ktor core dependency. Then add other dependencies such as the HTTP client engine dependency for processing and performing network requests. Since we are building on Android, we are adding Android specific functionality. For iOS, we would use an iOS dependency.


1 Answers

I was having the same exact issue as you. Built and works flawlessly for the Android app, but the iOS module cannot find any Ktor, coroutine, or kotlinx serialization class.

Following along https://github.com/adrianbukros/github-multiplatform-example, and trying to copy their set up, I finally got the packForXCode task to work by copying some of their gradle set up namely:

Their common module build.gradle, which should look like this:

apply plugin: 'kotlin-multiplatform'
apply plugin: 'kotlinx-serialization'

kotlin {
    targets {
        fromPreset(presets.jvm, "android")

        fromPreset(presets.iosX64, "ios_x86_64")
        fromPreset(presets.iosArm64, "ios_arm64")
        configure([ios_x86_64, ios_arm64]) {
            compilations.main.outputKinds("FRAMEWORK")
        }
    }

    sourceSets {
        commonMain.dependencies {
            implementation "org.jetbrains.kotlin:kotlin-stdlib"
            implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core-common:$coroutines_version"
            implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-common:$serialization_version"

            implementation "io.ktor:ktor-client-core:$ktor_version"
            implementation "io.ktor:ktor-client-json:$ktor_version"
        }

        androidMain.dependencies {
            implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
            implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"

            implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:$serialization_version"

            implementation "io.ktor:ktor-client-core-jvm:$ktor_version"
            implementation "io.ktor:ktor-client-json-jvm:$ktor_version"
        }

        iosMain.dependencies {
            implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core-native:$coroutines_version"
            implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-native:$serialization_version"

            implementation "io.ktor:ktor-client-ios:$ktor_version"
            implementation "io.ktor:ktor-client-core-ios:$ktor_version"
            implementation "io.ktor:ktor-client-json-ios:$ktor_version"
        }
        configure([ios_x86_64Main, ios_arm64Main]) {
            dependsOn iosMain
        }
    }
}

// workaround for https://youtrack.jetbrains.com/issue/KT-27170
configurations {
    compileClasspath
}

task packForXCode(type: Sync) {
    final File frameworkDir = new File(buildDir, "xcode-frameworks")

    final String configuration = project.findProperty("CONFIGURATION")?.toUpperCase() ?: "DEBUG"
    final String arch = project.findProperty("ARCHS") ?: "x86_64"

    dependsOn kotlin.targets."ios_${arch}".compilations.main.linkTaskName("FRAMEWORK", configuration)

    from { kotlin.targets."ios_${arch}".compilations.main.getBinary("FRAMEWORK", configuration).parentFile }
    into frameworkDir
}

tasks.build.dependsOn packForXCode

And their settings.gradle file which should look like this:

enableFeaturePreview("GRADLE_METADATA") // IMPORTANT!

include 'commoncode'
include 'app'

If it's still not working, look at all of their *.gradle and gradle.* files and see how yours are different.

That finally allowed the task to pass for me, and I can use my Kotlin Ktor code in XCode. However, now Android Studio is saying "Kotlin is not configured" for the shared iOS module. I will report back if I figure out why, and if you find something, please share!

EDIT Changed the common module build.gradle to look like this, seems like everything is working well!

apply plugin: 'kotlin-multiplatform'
apply plugin: 'kotlinx-serialization'

kotlin {
    targets {
        fromPreset(presets.jvm, 'android')

        // Change to `presets.iosArm64` to deploy the app to iPhone
        fromPreset(presets.iosX64, 'ios') {
            compilations.main.outputKinds('FRAMEWORK')
        }
    }

    sourceSets {
        commonMain.dependencies {
            implementation "org.jetbrains.kotlin:kotlin-stdlib"
            implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core-common:$coroutines_version"
            implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-common:$serialization_version"

            implementation "io.ktor:ktor-client-core:$ktor_version"
            implementation "io.ktor:ktor-client-json:$ktor_version"
        }

        androidMain.dependencies {
            implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
            implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"

            implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:$serialization_version"

            implementation "io.ktor:ktor-client-core-jvm:$ktor_version"
            implementation "io.ktor:ktor-client-json-jvm:$ktor_version"
        }

        iosMain.dependencies {
            implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core-native:$coroutines_version"
            implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-native:$serialization_version"

            implementation "io.ktor:ktor-client-ios:$ktor_version"
            implementation "io.ktor:ktor-client-core-ios:$ktor_version"
            implementation "io.ktor:ktor-client-json-ios:$ktor_version"
        }
    }
}

// workaround for https://youtrack.jetbrains.com/issue/KT-27170
configurations {
    compileClasspath
}

task packForXCode(type: Sync) {
    final File frameworkDir = new File(buildDir, "xcode-frameworks")
    final String mode = project.findProperty("XCODE_CONFIGURATION")?.toUpperCase() ?: 'DEBUG'

    inputs.property "mode", mode
    dependsOn kotlin.targets.ios.compilations.main.linkTaskName("FRAMEWORK", mode)

    from { kotlin.targets.ios.compilations.main.getBinary("FRAMEWORK", mode).parentFile }
    into frameworkDir

    doLast {
        new File(frameworkDir, 'gradlew').with {
            text = "#!/bin/bash\nexport 'JAVA_HOME=${System.getProperty("java.home")}'\ncd '${rootProject.rootDir}'\n./gradlew \$@\n"
            setExecutable(true)
        }
    }
}

tasks.build.dependsOn packForXCode
like image 166
Jeff Johnson Avatar answered Oct 05 '22 08:10

Jeff Johnson