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())
}
}
}
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
As of now kotlin multiplatform for iOS does not run on windows. This would explain why only the dependencies in your iosmain code wont resolve.
Related
I have been writing kotlin code for android apps for quite some time, but I decided to also start writing testing code too for my apps. I have been facing some problems though with the use of Hilt. What I tried is :
import android.app.Application
open class AbstractApplication: Application()
#HiltAndroidApp
class IgmeApplication : IgmeAbstractApplication() {
#Inject
lateinit var authenticationManager: AuthenticationManager
....
}
and then in the Android Test Directory:
import dagger.hilt.android.testing.CustomTestApplication
#CustomTestApplication(AbstractApplication::class)
open class HiltTestApplication
import android.app.Application
import android.content.Context
import androidx.test.runner.AndroidJUnitRunner
class HiltTestRunner : AndroidJUnitRunner() {
override fun newApplication(
cl: ClassLoader?,
className: String?,
context: Context?
): Application {
return super.newApplication(cl,HiltTestApp::class.java.name, context)
}
}
my Test class :
#HiltAndroidTest
class AuthenticationTest{
#get:Rule
var hiltRule = HiltAndroidRule(this)
#Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
Assert.assertEquals("com.crowdpolicy.onext.igme", appContext.packageName)
}
#Before
fun setUp() {
// Populate #Inject fields in test class
hiltRule.inject()
}
#After
fun tearDown() {
}
}
my app level gradle file :
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
id 'com.google.gms.google-services'
id 'com.google.firebase.crashlytics'
id 'com.google.firebase.firebase-perf' // Firebase Performance monitoring
id 'androidx.navigation.safeargs.kotlin'
id 'dagger.hilt.android.plugin'
id 'kotlin-parcelize'
id 'com.google.protobuf'
}
Properties localProperties = new Properties()
localProperties.load(new FileInputStream(rootProject.file('local.properties')))
Properties keyStoreProperties = new Properties()
keyStoreProperties.load(new FileInputStream(rootProject.file('keystore.properties')))
android {
buildToolsVersion "30.0.3"
ndkVersion localProperties['ndk.version']
signingConfigs {
release {
storeFile file(keyStoreProperties['key.release.path'])
keyAlias 'igme-key'
storePassword keyStoreProperties['key.release.keystorePassword']
keyPassword keyStoreProperties['key.release.keyPassword']
}
}
compileSdkVersion 30
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
freeCompilerArgs += '-Xopt-in=kotlin.RequiresOptIn'
}
defaultConfig {
applicationId "com.crowdpolicy.onext.igme"
minSdkVersion 21
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "com.crowdpolicy.onext.igme.HiltTestRunner"
}
buildTypes {
release {
minifyEnabled true
shrinkResources true
debuggable false
//signingConfig signingConfigs.release
firebaseCrashlytics {
// Enable processing and uploading o FirebaseCrashlytics.getInstance()f native symbols to Crashlytics
// servers. By default, this is disabled to improve build speeds.
// This flag must be enabled to see properly-symbolicated native
// stack traces in the Crashlytics dashboard.
nativeSymbolUploadEnabled true
unstrippedNativeLibsDir "$buildDir/ndklibs/libs"
}
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
ndk.debugSymbolLevel = "FULL" // Generate native debug symbols
}
}
packagingOptions {
exclude 'META-INF/DEPENDENCIES'
exclude 'META-INF/rxjava.properties'
}
kotlinOptions {
jvmTarget = '1.8'
}
testOptions {
unitTests {
includeAndroidResources = true
}
}
android.buildFeatures.viewBinding = true
dependencies {
// Testing-only dependencies
testImplementation 'junit:junit:4.13.2'
// Core library
androidTestImplementation 'androidx.test:core:1.4.0'
// AndroidJUnitRunner and JUnit Rules
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test:rules:1.4.0'
// Assertions
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.ext:truth:1.4.0'
// Espresso dependencies
androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_version"
androidTestImplementation "androidx.test.espresso:espresso-intents:$espresso_version"
androidTestImplementation "androidx.test.espresso:espresso-web:$espresso_version"
androidTestImplementation "androidx.test.espresso.idling:idling-concurrent:$espresso_version"
// The following Espresso dependency can be either "implementation"
// or "androidTestImplementation", depending on whether you want the
// dependency to appear on your APK's compile classpath or the test APK
// classpath.
androidTestImplementation "androidx.test.espresso:espresso-idling-resource:$espresso_version"
//Dagger
implementation "com.google.dagger:dagger:$dagger_version"
kapt "com.google.dagger:dagger-compiler:$dagger_version"
// region Hilt
implementation "com.google.dagger:hilt-android:$hilt_version"
implementation "androidx.hilt:hilt-navigation-fragment:$hilt_fragment_version"
kapt "com.google.dagger:hilt-android-compiler:$hilt_version" // or : kapt 'com.google.dagger:hilt-compiler:2.37'
// Testing Navigation
androidTestImplementation("androidx.navigation:navigation-testing:$nav_version")
// region Hilt testing - for instrumentation tests
androidTestImplementation "com.google.dagger:hilt-android-testing:$hilt_version"
// Make Hilt generate code in the androidTest folder
kaptAndroidTest "com.google.dagger:hilt-android-compiler:$hilt_version"
// endregion
// For local unit tests
testImplementation 'com.google.dagger:hilt-android-testing:2.37'
kaptTest 'com.google.dagger:hilt-compiler:2.37'
androidTestImplementation 'com.google.dagger:hilt-android-testing:2.37'
}
}
kapt {
correctErrorTypes true
javacOptions {
// These options are normally set automatically via the Hilt Gradle plugin, but we
// set them manually to workaround a bug in the Kotlin 1.5.20: https://github.com/google/dagger/issues/2684
option("-Adagger.fastInit=ENABLED")
option("-Adagger.hilt.android.internal.disableAndroidSuperclassValidation=true")
}
}
// https://github.com/google/protobuf-gradle-plugin
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:$protobuf_version"
// path = localProperties["protoc.dir"]
}
// Generates the java Protobuf-lite code for the Protobufs in this project. See
// https://github.com/google/protobuf-gradle-plugin#customizing-protobuf-compilation
// for more information.
generateProtoTasks {
all().each { task ->
task.builtins {
java {
option 'lite'
}
}
}
}
}
dependencies {
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
}
(Above I added only the dependencies used for testing )
project gradle
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = "1.5.20"
ext.google_version = '4.3.4'
ext.timberVersion = '4.7.1'
ext.event_bus = '3.2.0'
ext.gson_version = '2.8.6'
ext.retrofit2_version = '2.9.0'
ext.datastore_version = '1.0.0-rc02'
ext.rxkotlin_version = '3.0.1'
ext.rxandroid_version = '3.0.0'
ext.lifecycle_version = '2.3.1'
ext.dagger_version = '2.37'
ext.hilt_version = '2.37'
ext.hilt_fragment_version = '1.0.0'
ext.nav_version = '2.3.5'
ext.fragment_version = '1.3.6'
ext.androidXTestCoreVersion = '1.4.0'
ext.espresso_version = '3.4.0'
ext.lottie_version = '3.6.1'
ext.facebook_version = '9.0.0'
ext.protobuf_version = '3.15.8'
ext.protobuf_gradle_plugin_version = '0.8.16'
repositories {
google()
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:4.2.2"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.google.gms:google-services:$google_version"
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.7.1'
classpath 'com.google.firebase:perf-plugin:1.4.0'
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
classpath "com.google.protobuf:protobuf-gradle-plugin:$protobuf_gradle_plugin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
mavenCentral()
jcenter() // Warning: this repository is going to shut down soon
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
I followed the official documentation from dagger-hilt in this page : https://dagger.dev/hilt/testing.html, but I am still getting this error :
Caused by: java.lang.ClassCastException: com.crowdpolicy.onext.igme.HiltTestApplication cannot be cast to android.app.Application
at android.app.Instrumentation.newApplication(Instrumentation.java:997)
at android.app.Instrumentation.newApplication(Instrumentation.java:982)
at com.crowdpolicy.onext.igme.HiltTestRunner.newApplication(HiltTestRunner.kt:14)
at android.app.LoadedApk.makeApplication(LoadedApk.java:617)
and I do not know how to fix it, because I am really new to testing and it is the first time facing it !
line 14 in HIltTestRunner is :
return super.newApplication(cl,HiltTestApp::class.java.name, context)
I had the same Problem, while Testing i got a ClassCastException: HiltTestApplication cannot be cast to AbcApp (my Application class).
Solution is the #CustomTestApplication annotation, see Dagger Docs or Android Dev Docs and the use of the generated class
<interfacename>_Application.
Further on be aware that just using IgmeApplication (or AbcApp in my case) is not possible when it uses the #HiltAndroidApp annotation, see open Issue. Then you have to make an open class AbstractApplication: Application() like the questioner did. Which is then subclassed by your Application (AbcApp in my case or IgmeApplication in questioners case) and subclassed by the created class from annotation #CustomTestApplication. like:
#CustomTestApplication(AbstractApplication::class)
interface CustomTestApplicationForHilt
Note that interface is used instead of open class, but it also works with open class like the questioner did.
The created class is then CustomTestApplicationForHilt_Application which has to be used, when calling newApplication in your Testrunner class, like:
class HiltTestRunner : AndroidJUnitRunner() {
override fun newApplication(cl: ClassLoader?, className: String?, context: Context?): Application {
return super.newApplication(cl, CustomTestApplicationForHilt_Application::class.java.name, context)
}
}
Note that you can choose the name of the interface for the #CustomTestApplication annotation (here CustomTestApplicationForHilt) like you want, but the ending _Application will be added to the class name when the annotation is processed while building the app. Also note that the class will not be available before the build.
I am trying to develop a small library to post issues to my company's Jira server, and I thought that a Kotlin MPP w/ KTOR would be just the ticket.
At first, following a few tutorials, I made a shared project, and the imports for iOS were working fine but Android's Ktor implementation would not resolve. Then I realized that I needed to recreate the project and create a library instead of a shared application, as I have existing codebases for each mobile client already, and I need to publish the MPP library to be used by them.
Upon recreating the project as a library, and simply starting to add the dependencies for KTOR 1.3.2, the iOS dependencies are failing to resolve. This is not just KTOR, it is any iOS dependency, so there's obviously something incorrect in my project setup, but I am unable to spot it.
Here is the gradle file:
plugins {
id 'org.jetbrains.kotlin.multiplatform' version '1.3.72'
}
repositories {
jcenter()
mavenCentral()
maven { url "https://kotlin.bintray.com/kotlinx" }
}
group 'com.example.issuereporter'
version '0.0.1'
apply plugin: 'maven-publish'
kotlin {
targets {
final def iOSTarget = System.getenv('SDK_NAME')?.startsWith("iphoneos") ? presets.iosArm64 : presets.iosX64
fromPreset(iOSTarget, 'ios') {
binaries {
framework('IssueReporter')
}
}
fromPreset(presets.jvm, 'android')
}
def ktor_version = "1.3.2"
sourceSets["commonMain"].dependencies {
implementation kotlin('stdlib-common')
implementation "io.ktor:ktor-client-core:$ktor_version"
implementation "io.ktor:ktor-client-json:$ktor_version"
implementation "io.ktor:ktor-client-serialization:$ktor_version"
}
sourceSets["commonTest"].dependencies {
implementation kotlin('test-common')
implementation kotlin('test-annotations-common')
}
sourceSets["androidMain"].dependencies {
implementation kotlin('stdlib')
implementation "io.ktor:ktor-client-core-jvm:$ktor_version"
implementation "io.ktor:ktor-client-json-jvm:$ktor_version"
implementation "io.ktor:ktor-client-serialization-jvm:$ktor_version"
implementation "io.ktor:ktor-client-auth-jvm:$ktor_version"
}
sourceSets["androidTest"].dependencies {
implementation kotlin('test')
implementation kotlin('test-junit')
}
sourceSets["iosMain"].dependencies {
implementation "io.ktor:ktor-client-ios:$ktor_version"
implementation "io.ktor:ktor-client-core-native:$ktor_version"
implementation "io.ktor:ktor-client-json-native:$ktor_version"
implementation "io.ktor:ktor-client-serialization-native:$ktor_version"
}
}
configurations {
compileClasspath
}
task packForXcode(type: Sync) {
final File frameworkDir = new File(buildDir, "xcode-frameworks")
final String mode = project.findProperty("XCODE_CONFIGURATION")?.toUpperCase() ?: 'DEBUG'
final def framework = kotlin.targets.ios.binaries.getFramework("IssueReporter", mode)
inputs.property "mode", mode
dependsOn framework.linkTask
from { framework.outputFile.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
The console output is
Could not resolve io.ktor:ktor-client-ios:1.3.2.
Could not resolve io.ktor:ktor-client-core-native:1.3.2.
Could not resolve io.ktor:ktor-client-json-native:1.3.2.
Could not resolve io.ktor:ktor-client-serialization-native:1.3.2.
Anything obvious that I am missing here?
UPDATE
I scrapped and recreated the project w/ an updated version of IntelliJ (IntelliJ IDEA 2019.3.5 (Community Edition)) & the Kotlin 1.4.0 Plugin installed. This gave me a slightly different creation wizard, and the option to use Kotlin as the Gradle syntax.
Updated build.gradle.kts file:
plugins {
kotlin("multiplatform") version "1.4.0"
kotlin("plugin.serialization") version "1.4.0"
id("com.android.library")
id("kotlin-android-extensions")
}
group = "com.example.issuereporter"
version = "1.0-SNAPSHOT"
repositories {
gradlePluginPortal()
google()
jcenter()
mavenCentral()
maven(url = "https://kotlin.bintray.com/kotlinx")
maven(url = "https://dl.bintray.com/kotlin/ktor")
maven(url = "https://repo1.maven.org/maven2/")
maven(url = "https://dl.bintray.com/kotlin/kotlin-eap")
maven(url = "https://plugins.gradle.org/m2/")
}
kotlin {
android()
iosX64("ios") {
binaries {
framework {
baseName = "library"
}
}
}
val ktor_version = "1.3.2"
val serialization_version = "0.20.0"
sourceSets {
val commonMain by getting {
dependencies {
implementation("io.ktor:ktor-client-core:$ktor_version")
implementation("io.ktor:ktor-client-json:$ktor_version")
implementation("io.ktor:ktor-client-serialization:$ktor_version")
implementation("io.ktor:ktor-client-auth:$ktor_version")
implementation("io.ktor:ktor-client-apache:$ktor_version")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime-common:$serialization_version")
}
}
val commonTest by getting {
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
}
}
val androidMain by getting {
dependencies {
implementation("androidx.core:core-ktx:1.2.0")
implementation("io.ktor:ktor-client-android:$ktor_version")
implementation("io.ktor:ktor-client-auth-jvm:$ktor_version")
implementation("io.ktor:ktor-client-json-jvm:$ktor_version")
}
}
val androidTest by getting
val iosMain by getting {
dependencies {
implementation("io.ktor:ktor-client-ios:$ktor_version")
implementation ("io.ktor:ktor-client-core-native:$ktor_version")
implementation("io.ktor:ktor-client-json-native:$ktor_version")
implementation("io.ktor:ktor-client-auth-native:$ktor_version")
}
}
val iosTest by getting
}
}
android {
compileSdkVersion(29)
defaultConfig {
minSdkVersion(24)
targetSdkVersion(29)
versionCode = 1
versionName = "1.0"
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
}
}
}
The gradle dependencies for iOS successfully sync when I set the ktor_version to 1.3.2 but not for 1.4.0 (assuming the mirrors haven't updated for the native files??)... but the imports don't compile when I attempt to utilize the class at all... see attached image:
I would guess you don't have enableFeaturePreview("GRADLE_METADATA") in settings.gradle.
settings.gradle
Check our starter project KaMPKit for a running example on 1.3.72. We'll probably bump that to 1.4.0 this week, but for now it should be a good reference.
Here are my dependencies (I have separate iOS targets, but it should still help you):
iosEmulatorMain.dependencies {
implementation "io.ktor:ktor-client-ios:$ktorVersion"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core-native:$coroutinesVersion"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-native:$serializationVersion"
implementation "com.soywiz.korlibs.klock:klock-iosx64:$klockVersion"
implementation "com.github.aakira:napier-iosX64:$napierVersion"
}
iosDeviceMain.dependencies {
implementation "io.ktor:ktor-client-ios:$ktorVersion"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core-native:$coroutinesVersion"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-native:$serializationVersion"
implementation "com.soywiz.korlibs.klock:klock-iosarm64:$klockVersion"
implementation "com.github.aakira:napier-iosArm64:$napierVersion"
}
Versions:
ktorVersion=1.3.2
Repositories:
maven(url = "https://kotlin.bintray.com/kotlinx")
maven(url = "https://dl.bintray.com/kotlin/ktor")
maven(url = "https://repo1.maven.org/maven2/")
maven(url = "https://dl.bintray.com/kotlin/kotlin-eap")
maven(url = "https://plugins.gradle.org/m2/")
To configure client serializer try to write it like this:
install(JsonFeature) {
serializer = KotlinxSerializer(kotlinx.serialization.json.Json {
ignoreUnknownKeys = true
})
}
I made a simple protobuf+gRPC server/client example with Python, and wanted now to complete it with an Android Java client. However, I struggled through the documentation and got it mostly working (.proto compiles in Android Studio), BUT now I get Errors in the output *Grpc.java file (i.e. if I fix it it will just be overwritten by the compiler):
error: package io.grpc.protobuf does not exist
error: package com.google.protobuf.Descriptors does not exist
Since I get errors from io.gprc's and com.google's protobuf, I suspect a definition conflict in my gradle, but I cannot find/resolve it (went through several "tutorials" and it seemed common to use a mix of grpc and google sources).
Here is my build.gradle:
android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
defaultConfig {
applicationId "***.grpcclient"
minSdkVersion 21
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
implementation 'io.grpc:grpc-okhttp:1.25.0'
//implementation 'io.grpc:grpc-protobuf-lite:1.25.0'
implementation 'com.google.protobuf:protobuf-javalite:3.8.0'
implementation 'io.grpc:grpc-stub:1.25.0'
implementation 'org.glassfish:javax.annotation:10.0-b28'
}
apply plugin: 'com.google.protobuf'
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.10'
}
}
protobuf {
protoc {
artifact = 'com.google.protobuf:protoc:3.8.0'
}
plugins {
grpc {
artifact = 'io.grpc:protoc-gen-grpc-java:1.25.0'
}
}
generateProtoTasks {
all().each { task ->
task.builtins {
java {
option "lite"
}
python { }
}
task.plugins {
grpc {outputSubDir = 'java'}
}
}
}
}
Any help is appreciated!
Android uses Protobuf Lite, which is a subset of the normal implementation and optimized for Android. You correctly tried to depend on io.grpc:grpc-protobuf-lite:1.25.0. This provides the io.grpc.protobuf.lite package.
You also correctly configured protoc to generate protobuf-lite messages. However, the grpc plugin is generating "full" protobuf services. That's why you're seeing references to io.grpc.protobuf and classes missing in protobuf like com.google.protobuf.Descriptors.
protobuf {
...
generateProtoTasks {
all().each { task ->
...
task.plugins {
grpc {
outputSubDir = 'java'
option 'lite' // Needed this line
}
}
}
}
}
Since you are using protoc with the 'lite' option, instead of using the older protoc-gen-javalite plugin, you are using the correct protobuf-javalite dependency for protobuf. However, grpc-java 1.25.0 depends on protobuf-lite which will collide. This is discussed some in Issue 6405 and will be fixed in grpc-java 1.26.0. But for the moment you'll need to exclude the protobuf-lite dependency brought in by grpc-protobuf-lite.
dependencies {
...
implementation ('io.grpc:grpc-protobuf-lite:1.25.0') {
// Exclude will not be necessary starting in grpc 1.26
exclude group: 'com.google.protobuf', module: 'protobuf-lite'
}
}
My working build.gradle:
dependencies {
implementation 'com.squareup.okhttp:okhttp:2.7.5'
implementation 'javax.annotation:javax.annotation-api:1.3.2'
implementation 'io.grpc:grpc-core:1.27.1'
implementation 'io.grpc:grpc-stub:1.27.1'
implementation 'io.grpc:grpc-okhttp:1.27.1'
implementation('io.grpc:grpc-protobuf-lite:1.27.1') {
exclude module: "protobuf-lite"
}
}
protobuf {
protoc {
artifact = 'com.google.protobuf:protoc:3.11.4'
}
plugins {
grpc {
artifact = 'io.grpc:protoc-gen-grpc-java:1.27.1'
}
javalite {
artifact = 'com.google.protobuf:protoc-gen-javalite:3.0.0'
}
}
generateProtoTasks {
all().each { task ->
task.builtins {
java {
option 'lite'
}
}
task.plugins {
grpc {
// Options added to --grpc_out
option 'lite'
}
}
}
}
}
I wonder if is possible to access the Android context application in androidMain sourceSets with the kotlin-multiplatform plugin.
Here is the build.gradle file
apply plugin: 'kotlin-multiplatform'
kotlin {
targets {
final def iOSTarget = System.getenv('SDK_NAME')?.startsWith("iphoneos") \
? presets.iosArm64 : presets.iosX64
fromPreset(iOSTarget, 'iOS') {
compilations.main.outputKinds('FRAMEWORK')
}
fromPreset(presets.jvm, 'android')
}
sourceSets {
commonMain {
kotlin.srcDir('src')
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlin_version"
// coroutine
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core-common:1.1.0"
}
}
androidMain {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
// Timber
implementation "com.jakewharton.timber:timber:$timber_version"
// coroutine
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.0"
}
}
}
}
I have tried add the 'com.android.application' plugin into androidMain source, but the sync fails.
Thanks in advance
Yes, you can.
In your build.gradle file you are not targeting android as such (presets.android) but the jvm (presets.jvm) and calling it android.
As it is now, what your build will generated is a JAR file not an aar file.
If you want to target android you have also to use the android gradle plugin. To be able to use the android framework from the androidMain folder, update your build.gradle file. Below an example:
apply plugin: 'org.jetbrains.kotlin.multiplatform'
apply plugin: 'com.android.library'
android {
compileSdkVersion 28
defaultConfig {
minSdkVersion 21
targetSdkVersion 28
versionCode 1
versionName '1.0'
}
buildTypes {
release {
minifyEnabled false
}
}
// By default the android gradle plugin expects to find the kotlin source files in
// the folder `main` and the test in the folder `test`. This is to be able place
// the source code files inside androidMain and androidTest folders
sourceSets {
main {
manifest.srcFile 'src/androidMain/AndroidManifest.xml'
java.srcDirs = ['src/androidMain/kotlin']
res.srcDirs = ['src/androidMain/res']
}
test {
java.srcDirs = ['src/androidTest/kotlin']
res.srcDirs = ['src/androidTest/res']
}
}
}
dependencies {
implementation "com.jakewharton.timber:timber:$timber_version
}
kotlin {
targets {
final def iOSTarget = System.getenv('SDK_NAME')?.startsWith("iphoneos") \
? presets.iosArm64 : presets.iosX64
fromPreset(iOSTarget, 'iOS') {
compilations.main.outputKinds('FRAMEWORK')
}
fromPreset(presets.android, 'android')
}
sourceSets {
commonMain {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlin_version"
// coroutine
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core-common:1.1.0"
}
}
androidMain {
dependencies {
api "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}
}
}
}
task buildiOSFramework(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)
}
}
}
Having experienced this problem in a separate project, I made a test project to verify the issue.
Opening Android Studio 3.0, I created a new basic project. Its main module was called app.
I added a library module, called libraryone.
In libraryone, I added a dependency to Gson, and added a single class with a single static method using Gson.
In app, I added a dependency to libraryone. I tested it plain, as well as with transitive = true, as the internet seemed to vaguely concur that doing so might help. (It did not.)
In app, in MainActivity, I used the class from libraryone. This worked (as long as I didn't have transitive = false set, instead).
However, I cannot reference Gson from MainActivity itself. It gives a "Cannot resolve symbol 'Gson'" error, both from Android Studio, as well as from the command line Gradle. This is...unusual and aggravating, as it means you have to repeat common dependencies all throughout a group of projects, and the classes are included in the output anyway. Surely either I'm doing something wrong, or this is a bug?
My code is as follows:
MainActivity.java (in app)
package com.erhannis.test.dependencytest;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import com.erhannis.test.libraryone.LibClass;
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String s = LibClass.convert(new Object());
System.out.println("out: " + s);
new com.google.gson.Gson(); // This line gives a compile-time error: "Cannot resolve symbol 'Gson'"
}
}
LibClass.java (in libraryone)
package com.erhannis.test.libraryone;
import com.google.gson.Gson;
public class LibClass {
public static String convert(Object o) {
Gson gson = new Gson();
return gson.toJson(o);
}
}
build.gradle (app)
apply plugin: 'com.android.application'
android {
compileSdkVersion 26
defaultConfig {
applicationId "com.erhannis.test.dependencytest"
minSdkVersion 18
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
implementation (project(path: ':libraryone'))
}
build.gradle (libraryone)
apply plugin: 'com.android.library'
android {
compileSdkVersion 26
defaultConfig {
minSdkVersion 18
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:26.1.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
implementation 'com.google.code.gson:gson:2.8.2'
}
It is because you declared the transitive dependency as implementation. Use api instead of implementation change:
implementation 'com.google.code.gson:gson:2.8.2'
to:
api 'com.google.code.gson:gson:2.8.2'
The reason why you should declare the transitive dependency in your library can be found here
You are using:
implementation (project(path: ':libraryone'))
implementation 'com.google.code.gson:gson:2.8.2'
Check the official doc:
When your module configures an implementation dependency, it's letting Gradle know that the module does not want to leak the dependency to other modules at compile time. That is, the dependency is available to other modules only at runtime.
Here you can find a very good blog about it.
The best solution for this is to create a private Github/Gitlab repository for the aar dependency. You can also make it public if you want. The aar file does not contain transitive dependencies when used locally. We need to publish it somewhere with the .pom file. Here's how to do it,
create a personal access token in Github
Github -> Settings -> Developer Settings -> Personal Access token - with write:packages / read:packages access level
(create two tokens if you need to distribute privately
Add id 'maven-publish' to plugins in module level gradle
Add the below code to module level gradle
task sourceJar(type: Jar) {
from android.sourceSets.main.java.srcDirs
archiveClassifier.set("sources")
}
publishing {
publications {
bar(MavenPublication) {
groupId 'com.your'
artifactId 'artifact'
version '1.0'
artifact sourceJar
artifact("$buildDir/outputs/aar/YourLibraryName-release.aar")
pom.withXml {
final dependenciesNode = asNode().appendNode('dependencies')
ext.addDependency = { Dependency dep, String scope ->
if (dep.group == null || dep.version == null || dep.name == null || dep.name == "unspecified")
return
final dependencyNode = dependenciesNode.appendNode('dependency')
dependencyNode.appendNode('groupId', dep.group)
dependencyNode.appendNode('artifactId', dep.name)
dependencyNode.appendNode('version', dep.version)
dependencyNode.appendNode('scope', scope)
if (!dep.transitive) {
final exclusionNode = dependencyNode.appendNode('exclusions').appendNode('exclusion')
exclusionNode.appendNode('groupId', '*')
exclusionNode.appendNode('artifactId', '*')
} else if (!dep.properties.excludeRules.empty) {
final exclusionNode = dependencyNode.appendNode('exclusions').appendNode('exclusion')
dep.properties.excludeRules.each { ExcludeRule rule ->
exclusionNode.appendNode('groupId', rule.group ?: '*')
exclusionNode.appendNode('artifactId', rule.module ?: '*')
}
}
}
configurations.compile.getDependencies().each { dep -> addDependency(dep, "compile") }
configurations.api.getDependencies().each { dep -> addDependency(dep, "compile") }
configurations.implementation.getDependencies().each { dep -> addDependency(dep, "runtime") }
}
}
}
repositories {
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/GITHUB_USERID/REPOSITORY")
credentials {
username = "GITHUB_USERID" // Better if you use env variable
password = "WRITE_ACCESS_TOKEN"
}
}
}
}
In the Gradle toolbar you can find publish under the publishing subdirectory in your library module directory. ( Make sure to build the aar from build->build in module directory before publish)
To use it in your project, add following to your app level gradle file inside android{ }tag
repositories {
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/GITHUB_USERID/REPOSITORY")
credentials {
username = "GITHUB_USERID" // Better if you use env variable
password = "READ_ACCESS_TOKEN"
}
}
}
Finally in dependencies
implementation 'com.your:artifact:1.0'
*Gitlab is also pretty similar to this