I am trying to use Android logs in my shared code so wanted to make use of the 'expected/actual' functionality in order to make the android side use logs to be read in log cat. However I cannot get the android module(not app module) to import the android.util.Log.
I have seen this answer but it did not work for me. I cannot get the import to resolve.
I think I need to implement a specific dependency in order to have access to the import but I'm not sure what that is.
Here is my build.gradle.kts
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
plugins {
kotlin("multiplatform")
}
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("io.ktor:ktor-client:1.0.0-beta-3")
implementation ("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:1.3.2")
// implementation ("org.jetbrains.kotlinx:kotlinx-serialization-runtime-common:0.14.0")
}
sourceSets["androidMain"].dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib") //Allows _androidMain to have java imports
implementation("io.ktor:ktor-client-android:1.0.0-beta-3")
api("org.jetbrains.kotlin:kotlin-stdlib:1.3.61")
api("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.61")
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.61")
}
}
val packForXcode by tasks.creating(Sync::class) {
val targetDir = File(buildDir, "xcode-frameworks")
/// selecting the right configuration for the iOS
/// framework depending on the environment
/// variables set by Xcode build
val mode = System.getenv("CONFIGURATION") ?: "DEBUG"
val framework = kotlin.targets
.getByName<KotlinNativeTarget>("ios")
.binaries.getFramework(mode)
inputs.property("mode", mode)
dependsOn(framework.linkTask)
from({ framework.outputDirectory })
into(targetDir)
/// generate a helpful ./gradlew wrapper with embedded Java path
doLast {
val gradlew = File(targetDir, "gradlew")
gradlew.writeText("#!/bin/bash\n"
+ "export 'JAVA_HOME=${System.getProperty("java.home")}'\n"
+ "cd '${rootProject.rootDir}'\n"
+ "./gradlew \$#\n")
gradlew.setExecutable(true)
}
}
tasks.getByName("build").dependsOn(packForXcode)
Here you got JVM target with the name "android" instead of actually Android target. The same problem occurred in the linked question. Can you tell, what's going on when you use the script from the answer? It seems like that one should work correctly.
As described in the documentation, one has to use an Android-specific Gradle plugin to make the Android target available. If you want to see how it can be done, consider having a look at this sample.
I had the same problem. Try using android() instead of only the jvm("android").
Also I've added my dependencies to android with android.sourceSets.foreach{ _ ->
dependencies{ ... }
}
Just fixed same issue, finally used this tutorial https://medium.com/icerock/how-to-start-use-kotlin-multiplatform-for-mobile-development-1d3022742178
so my build.gradle looks like:
apply plugin: 'com.android.library'
apply plugin: 'org.jetbrains.kotlin.multiplatform'
android {
compileSdkVersion 29
defaultConfig {
minSdkVersion 14
targetSdkVersion 29
}
}
kotlin {
targets {
android()
iosArm64()
iosX64()
}
sourceSets {
commonMain {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlin_version"
}
}
androidMain {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}
}
}
}
Related
I created a gradle plugin to unify some settings between the various modules of the application. the summary of the error is this:
org.gradle.api.plugins.InvalidPluginException: An exception occurred applying plugin request [id: 'common.plugin']
Caused by: org.gradle.api.UnknownDomainObjectException: Extension of type 'LibraryExtension' does not exist. Currently registered extension types: [ExtraPropertiesExtension,....]
This is a summary image of the project architecture:
CommonPluginClass:
class CommonPluginClass : Plugin<Project> {
private val consumerProguardFileName = "consumer-rules.pro"
private val proguardFileName = "proguard-rules.pro"
private val sdkToCompile = 33
override fun apply(project: Project) {
println(">>> Adding sugar to gradle files!")
with(project)
{
applyPlugins(this)
androidConfig(this)
}
println(">>> Sugar added for core Module!")
}
private fun applyPlugins(project: Project) {
println(">>> apply plugins!")
project.pluginManager.run {
apply("com.android.application")
apply("kotlin-android")
apply("kotlin-kapt")
}
println(">>> end apply plugins!")
}
private fun androidConfig(project: Project) {
project.extensions.configure<LibraryExtension>{
defaultConfig.targetSdk = 33
}
}
}
the error occurs inside the androidConfig function when calling configure
I was inspired by now android, dependencies and imports are similar but not build. Can someone unlock it please.
build-logic:convention build.gradle
plugins {
alias(libs.plugins.kotlin.jvm) apply false
id "org.gradle.kotlin.kotlin-dsl" version "2.4.1"
}
dependencies {
compileOnly(libs.android.pluginGradle)
compileOnly(libs.kotlin.pluginGradle)
}
gradlePlugin {
plugins {
commonPlugin {
id = "common.plugin"
implementationClass = "CommonPluginClass"
}
}
}
LITTLE UPDATE:
I've noticed that any module I enter always identifies it to me as ApplicationExtension
I found the solution and as usual it's a stupid thing.
the application extension use plugin
apply("com.android.application")
the library or module use plugin
apply("com.android.library")
I have two multiplatform modules shared and other in a standard multiplatform template project that targets Android and iOS.
shared defines a class in commonMain source set
class SharedGreeting()
other is setup to depend on shared like this in the gradle file:
val commonMain by getting {
dependencies {
implementation(project(":shared"))
}
}
And in its androidMain sourceset it tries to reference SharedGreeting in some class, fx:
class AndroidGreeter{
val foo = SharedGreeting()
}
But no matter what I try, I get IDE errors when I try to reference the shared class, and I have to manually add an import statement.
The code compiles and deploys without problems though!
Any ideas on what I am missing or misunderstanding? Or is this a bug in KMM?
Full copy of other gradle file:
plugins {
kotlin("multiplatform")
kotlin("native.cocoapods")
id("com.android.library")
}
version = "1.0"
kotlin {
android()
iosX64()
iosArm64()
iosSimulatorArm64()
cocoapods {
summary = "Some description for the Shared Module"
homepage = "Link to the Shared Module homepage"
ios.deploymentTarget = "14.1"
framework {
baseName = "other"
}
}
sourceSets {
val commonMain by getting {
dependencies {
implementation(project(":shared"))
}
}
val commonTest by getting {
dependencies {
implementation(kotlin("test"))
}
}
val androidMain by getting
val androidTest by getting
val iosX64Main by getting
val iosArm64Main by getting
val iosSimulatorArm64Main by getting
val iosMain by creating {
dependsOn(commonMain)
iosX64Main.dependsOn(this)
iosArm64Main.dependsOn(this)
iosSimulatorArm64Main.dependsOn(this)
}
val iosX64Test by getting
val iosArm64Test by getting
val iosSimulatorArm64Test by getting
val iosTest by creating {
dependsOn(commonTest)
iosX64Test.dependsOn(this)
iosArm64Test.dependsOn(this)
iosSimulatorArm64Test.dependsOn(this)
}
}
}
android {
compileSdk = 32
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig {
minSdk = 24
targetSdk = 32
}
}
For full project source code:
https://github.com/RabieJradi/kmm_import_error_sample
IDE suggested action for adding a dependency doesn't unfortunately do anything.
Turns out it is a bug in KMM and a fix have been implemented by the Jetbrains team here https://youtrack.jetbrains.com/issue/KTIJ-22056/KMM-Cant-reference-shared-module-commonMain-source-set-classes-from-another-modules-androidMain-source-set
You cannot do this in that way.
To make your project work you have to work with expect and actual words.
So, make these changes:
change your SharedGreeting class in shared module like that:
expect class SharedGreeting {
fun greeting(): String
}
Then your IDE wants you to make two changes, in shared module.
Add a class in both modules, iosMain and androidMain named SharedGreeting. The code is the same for both classes:
actual class SharedGreeting {
actual fun greeting(): String {
return "Hello, ${Platform().platform}!"
}
}
The work is done, now your other library has no errors. From your androidMain module inside "other" you can work only on other androidMain modules inside other MKK libraries.
I followed the Targeting iOS and Android with Kotlin Multiplatform tutorial and was able to setup the working project for both ios and android.
As a next step I want to use android specific libs inside src/androidMain/. Example:
package com.test.android
import android.os.Build
actual fun getBuildInfo(): String {
return Build.MODEL
}
But for the import I receive: Unresolved reverence: os
Question: Which additional steps are needed to use android specific libs inside androidMain?
The: build.gradle.kts
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
plugins {
kotlin("multiplatform")
}
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 = "multi_module_be_connection"
}
}
}
jvm("android")
sourceSets["commonMain"].dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-common")
}
sourceSets["androidMain"].dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib")
}
}
val packForXcode by tasks.creating(Sync::class) {
val targetDir = File(buildDir, "xcode-frameworks")
/// selecting the right configuration for the iOS
/// framework depending on the environment
/// variables set by Xcode build
val mode = System.getenv("CONFIGURATION") ?: "DEBUG"
val framework = kotlin.targets
.getByName<KotlinNativeTarget>("ios")
.binaries.getFramework(mode)
inputs.property("mode", mode)
dependsOn(framework.linkTask)
from({ framework.outputDirectory })
into(targetDir)
/// generate a helpful ./gradlew wrapper with embedded Java path
doLast {
val gradlew = File(targetDir, "gradlew")
gradlew.writeText("#!/bin/bash\n"
+ "export 'JAVA_HOME=${System.getProperty("java.home")}'\n"
+ "cd '${rootProject.rootDir}'\n"
+ "./gradlew \$#\n")
gradlew.setExecutable(true)
}
}
tasks.getByName("build").dependsOn(packForXcode)
Source of the project is here: https://github.com/kotlin-hands-on/mpp-ios-android
In order to make use of android specific API in a MPP module you can include the android lib plugin:
The: build.gradle.kts
plugins {
id("com.android.library")
kotlin("multiplatform")
}
When doing so it's mandatory to include an AndroidManifest.xml, this can be placed in your src/androidMain/ and by including it in your script for the android build with:
The: build.gradle.kts
android {
sourceSets {
getByName("main") {
manifest.srcFile ("src/androidMain/AndroidManifest.xml")
}
}
}
The: AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest package="org.path.to.package.common"/>
NOTE: change the package path.
I want to write a common library using Kotin multiplatform that can be used on android and on ios.
This library will have dependencies for each platform, for eg: on android I want to add jsoup as a dependency and on ios I want to add swiftsoup
For android adding java libraries as dependencies is rather easy, but for ios I could not find a way.
The question is: how can I add a swift library as a dependency to this project for ios?
or can somebody point me to a working project as an example? I could not find anything on the internet that could solve my issue.
build.gradle.kts :
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
val serializationVersion = "0.20.0"
val kotlinVersion = "1.3.72"
plugins {
kotlin("multiplatform") version kotlinVersion
kotlin("plugin.serialization") version kotlinVersion
}
buildscript {
dependencies {
classpath("org.jetbrains.kotlin:kotlin-serialization:$kotlinVersion")
}
}
repositories {
jcenter()
mavenCentral()
}
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-serialization-runtime-common:$serializationVersion")
}
sourceSets["androidMain"].dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib")
implementation("org.jsoup:jsoup:1.13.1")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime:$serializationVersion")
}
sourceSets["iosMain"].dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime-native:$serializationVersion")
}
}
val packForXcode by tasks.creating(Sync::class) {
group = "build"
//selecting the right configuration for the iOS framework depending on the Xcode environment variables
val mode = System.getenv("CONFIGURATION") ?: "DEBUG"
val framework = kotlin.targets.getByName<KotlinNativeTarget>("ios").binaries.getFramework(mode)
inputs.property("mode", mode)
dependsOn(framework.linkTask)
val targetDir = File(buildDir, "xcode-frameworks")
from({ framework.outputDirectory })
into(targetDir)
doLast {
val gradlew = File(targetDir, "gradlew")
gradlew.writeText("#!/bin/bash\nexport 'JAVA_HOME=${System.getProperty("java.home")}'\ncd '${rootProject.rootDir}'\n./gradlew \$#\n")
gradlew.setExecutable(true)
}
}
tasks.getByName("build").dependsOn(packForXcode)
You can't use Swift dependencies in Kotlin unless they are compatible with Objective-C. If you want to talk to them directly, you'll need to point to them with cinterop. Alternatively, you can create interfaces in Kotlin, or take lambdas, that are implemented by Swift code, and avoid cinterop.
https://kotlinlang.org/docs/reference/native/objc_interop.html
We pass in a lot of implementations in one of our example apps: https://github.com/touchlab/DroidconKotlin/blob/master/iosApp/iosApp/app/AppDelegate.swift#L33
I have three targets commonMain/androidMain/iOSMain respectively. Because I need to access the assets in Android devices in androidMain module. I found I cannot use the Android API... The following is part of my build.gradle.kts:
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
plugins {
id("com.android.application")
kotlin("multiplatform")
}
repositories {
google()
jcenter()
}
android {
compileSdkVersion(29)
buildToolsVersion("29.0.1")
defaultConfig {
minSdkVersion(19)
targetSdkVersion(29)
}
sourceSets {
getByName("main") {
manifest.srcFile("src/androidMain/AndroidManifest.xml")
java.srcDirs(file("src/androidMain/kotlin"))
res.srcDirs(file("src/androidMain/res"))
}
}
}
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 = "Example"
}
}
}
jvm("android")
sourceSets["commonMain"].dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-common")
}
sourceSets["androidMain"].dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib")
}
sourceSets["commonTest"].dependencies {
implementation ("org.jetbrains.kotlin:kotlin-test")
implementation ("org.jetbrains.kotlin:kotlin-test-junit")
}
}
How can I use Android library in androidMain? For example,
val inputStream = assets.open("Test.txt")
Make sure to create an AndroidManifest.xml with (library module) package name different than the (app module) package name underneath the SharedCode/src directory. And as Nagy Robi said use android() instead of jvm('android'). Please check the below references also:
Adding an Android Target to a Kotlin Multiplatform Project
Multiplatform sample
Try using android() instead of jvm('android') to load the target from the presets.