I have created a kotlin shared library project (using Android Studio on Windows), the android side of things work fine, but for some reason when writing iOS specific code in Kotlin, I can't seem to import the platform libraries. I was following the instructions from here to get started.
I did put include ':Shared' in my settings.gradle and also this implementation project(':Shared') in my app build.gradle
My end goal is to have the library just have business logic that will be shared between iOS and Android. I'm just trying to get an example project running, so that I know it works.
This is how my file structure is:
My build.gradle for the Shared module:
apply plugin: 'java-library'
apply plugin: 'kotlin-multiplatform'
/*We are doing three things in the codebase below:
1. Listing out the target for the shared code. For Android, JVM target.
For iOS, target depends on the device type, i.e. simulator or a real device.
2. We have defined iOS, Android and common source sets, which will allow different
configuration for each source set.
3. We have created a task for Xcode to generate framework and add it to our iOS project.
*/
kotlin{
targets{
// //Xcode sets SDK_NAME environment variable - based on whether the
// //target device is a simulator or a real device, the preset should vary
final def iOSTarget = System.getenv('SDK_NAME')?.startsWith("iphoneos") \
? presets.iosArm64 : presets.iosX64
//outputKinds - FRAMEWORK would mean that the shared code would be exported as a FRAMEWORK
// EXECUTABLE - produces a standalone executable that can be used to run as an app
fromPreset(iOSTarget, 'ios'){
binaries{
framework('Shared')
}
}
//create a target for Android from presets.jvm
fromPreset(presets.jvm, 'android')
}
//we have 3 different sourceSets for common, android and iOS.
//each sourceSet can have their own set of dependencies and configurations
sourceSets{
commonMain.dependencies{
api 'org.jetbrains.kotlin:kotlin-stdlib-common'
}
androidMain.dependencies{
api 'org.jetbrains.kotlin:kotlin-stdlib'
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
iosMain{
}
}
}
configurations{
compileClasspath
}
// This task attaches native framework built from ios module to Xcode project
// Don't run this task directly,
// Xcode runs this task itself during its build process when we configure it.
// make sure all Gradle infrastructure exists (gradle.wrapper, gradlew)
//and gradlew is in executable mode (chmod +x gradlew)
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("Shared", 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
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
commonMain code:
//This function will be the general function declaration that will be used as
//actual in our platform specific code.
expect fun getCurrentDate() : String
//This is the common function that will be called by Android and iOS app
fun getDate():String{
return "Current Date is ${getCurrentDate()}"
}
androidMain code:
import java.util.*
actual fun getCurrentDate(): String{
return Date().toString()
}
iosMain code (this is where the issue is):
//can't get this import to work.
//import platform.Foundation.NSDate
actual fun getCurrentDate(): String{
//return NSDate().toString()
return ""
}
My android MainActivity (this works) :
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.TextView
import getDate// function from our Shared Module
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<TextView>(R.id.dateLbl).text = getDate()
}
}
Any help or advice as to why the platform libraries won't work will be highly appreciated.
I just tried this on Windows, and the iOS platforms don't appear to be available, even just for importing. I don't think you'll be able to edit Apple targets on Windows. The available targets locally are:
android_arm32/
android_arm64/
android_x64/
android_x86/
linux_arm32_hfp/
linux_arm64/
linux_x64/
mingw_x64/
mingw_x86/
You can see local targets here: ~/.konan/kotlin-native-windows-1.3.72/klib/platform
Related
I'm building a multiplatform library for Android and iOS. My gradle file looks like this:
plugins {
id 'org.jetbrains.kotlin.multiplatform' version '1.4.0'
}
repositories {
mavenCentral()
}
group 'com.example'
version '0.0.1'
apply plugin: 'maven-publish'
kotlin {
jvm()
// This is for iPhone simulator
// Switch here to iosArm64 (or iosArm32) to build library for iPhone device
ios {
binaries {
framework()
}
}
sourceSets {
commonMain {
dependencies {
implementation kotlin('stdlib-common')
implementation("com.ionspin.kotlin:bignum:0.2.2")
}
}
commonTest {
dependencies {
implementation kotlin('test-common')
implementation kotlin('test-annotations-common')
}
}
jvmMain {
dependencies {
implementation("com.ionspin.kotlin:bignum:0.2.2")
}
}
jvmTest {
dependencies {
implementation kotlin('test')
implementation kotlin('test-junit')
}
}
iosMain {
}
iosTest {
}
}
}
configurations {
compileClasspath
}
Im using a third party library and I'm using it like this:
fun test(value: String): Int {
return BigDecimal.parseString(value).toBigInteger().intValue()
}
The problem is when I build the .jar the bignum library isn't included, and when I use the lib in an Android project I get an exception ClassNotFoundException: Didn't find class "com.ionspin.kotlin.bignum.decimal.BigDecimal".
Is there a way to include third party libs in the .jar for Android and .framework for iOS?
JVM
So, the only way I've found to generate a Fat JAR that works like you expect is by adding two custom gradle tasks in project:build.gradle.kts of your KMP library after appling the java plugin.
plugins {
[...]
id("java")
}
[...]
kotlin {
jvm {
[...]
compilations {
val main = getByName("main")
tasks {
register<Copy>("unzip") {
group = "library"
val targetDir = File(buildDir, "3rd-libs")
project.delete(files(targetDir))
main.compileDependencyFiles.forEach {
println(it)
if (it.path.contains("com.")) {
from(zipTree(it))
into(targetDir)
}
}
}
register<Jar>("fatJar") {
group = "library"
manifest {
attributes["Implementation-Title"] = "Fat Jar"
attributes["Implementation-Version"] = archiveVersion
}
archiveBaseName.set("${project.name}-fat")
val thirdLibsDir = File(buildDir, "3rd-libs")
from(main.output.classesDirs, thirdLibsDir)
with(jar.get() as CopySpec)
}
}
tasks.getByName("fatJar").dependsOn("unzip")
}
}
[...]
}
You then must launch the fatJar gradle task that generate a .jar file with the 3rd libraries classes extracted from they corresponding jar archives.
You can customize the two custom gradle scripts even more in order to better fit your needs (here I only included com. package name starting deps).
Then in your Android app app:build.gradle file you can use it as you did or simply
implementation files('libs/KMLibraryTest001-fat-1.0-SNAPSHOT.jar')
iOS
As you ask also for the iOS part in your title (even if it's a second citizen in the main topic of your question) you need only to use api instead of implementation for your 3rd party library along with the export option of the framework.
ios() {
binaries {
framework() {
transitiveExport = true // all libraries
//export(project(":LibA")) // this library project in a trainsitive way
//export("your 3rd party lib") // this 3rd party lib in a transitive way
}
}
}
And you can find a full reference here.
If you see the Krypto library, it has
androidMain
jsMain
jvmMain
mingwX64Main
nativPosixMain
Which means 5 kind of binaries are generated to support 5 platforms
Convincingly, this explains that each platform expects its own binary
for example,
windows -- DLL file
linux -- so file
java -- JAR file
mac -- dylib file
A JAR gets loaded into JVM, but IOS does not use JVM
Separate your Utility functions which has a common logic and write gradle to target multiple platforms
If you want to start with pure multiplatform, you can try this Official Example
Or create a sub gradle module and create a library project which is common to IOS as well as Android
The possible targets are properly documented here
I have created a application which publishes the binary to local repository and re-uses in the MainActivity -- you can get the code here
modify the local.properties for android SDK location and use
gradlew assemble
to build the APK and test it yourself
open the mylib\build.gradle.kts folder and you can see the targets jvm and iosX64 , jvm is used for android
If I'm correct using api instead of implementation should fix your problem, though I didn't try it out yet on the Native part
See Api and implementation separation
What I'm trying to achieve
I'm trying to generate my REST API client for Android using OpenAPI Generator from the build.gradle script. That way, I wouldn't have to run the generator command line every time the specs change. Ideally, this would be generated when I build/assemble my app, and the sources would end up in the java (generated) folder, where generated sources are then accessible from the code (this is what happens with the BuildConfig.java file for example).
What I've tried so far
Following this link from their official GitHub, here's the build.gradle file I ended up with:
apply plugin: 'com.android.application'
apply plugin: 'org.openapi.generator'
...
openApiValidate {
inputSpec = "$rootDir/app/src/main/openapi/my-api.yaml"
recommend = true
}
openApiGenerate {
generatorName = "java"
inputSpec = "$rootDir/app/src/main/openapi/my-api.yaml"
outputDir = "$buildDir/generated/openapi"
groupId = "$project.group"
id = "$project.name-openapi"
version = "$project.version"
apiPackage = "com.example.mypackage.api"
invokerPackage = "com.example.mypackage.invoker"
modelPackage = "com.example.mypackage.model"
configOptions = [
java8 : "true",
dateLibrary : "java8",
library : "retrofit2"
]
}
...
First, I've never managed to get the API generated with the build/assemble task, even when I tried adding:
compileJava.dependsOn tasks.openApiGenerate
or
assemble.dependsOn tasks.openApiGenerate
The only way I could generate the sources was by manually triggering the openApiGenerate task:
Then, when I do generate my sources this way, they end up in the build folder but aren't accessible from my code, and aren't visible in the java (generated) folder:
I then have to manually copy/paste the generated source files to my project sources in order to use the API.
Even though I'm able to work around these issues by adding manual procedures, it would be way more maintainable if the whole process was simply automatic. I was able to achieve a similar result with another tool, Protobuf. Indeed, my gradle task gets triggered every time I build the app, and the sources end up in the java (generated) folder, so I don't have to do any additional work. The task is much simpler though, so I assume the main work that I'm not able to replicate with OpenAPI Generator is handled by the Protobuf plugin itself.
You have to specify path to the generated sources as a custom source set for your Gradle module, which is app in this case, as described here – https://developer.android.com/studio/build/build-variants#configure-sourcesets. That way Gradle will treat your sources as accessible from your code.
Something like this:
android {
...
sourceSets {
main {
java.srcDirs = ['build/generated/openapi/src/main/java']
}
}
...
}
I solved the issue you described like this, I'm using gradle.kts however.
See my build.gradle.kts
plugins {
// Your other plugins
id("org.openapi.generator") version "5.3.0"
}
openApiGenerate {
generatorName.set("kotlin")
inputSpec.set("$rootDir/app/src/main/openapi/my-api.yaml")
outputDir.set("$buildDir/generated/api")
// Your other specification
}
application {
// Your other code
sourceSets {
main {
java {
// TODO: Set this path according to what was generated for you
srcDir("$buildDir/generated/api/src/main/kotlin")
}
}
}
}
tasks.compileKotlin {
dependsOn(tasks.openApiGenerate)
}
You need to build the application at least once for the IDE to detect the library (at least this is the case for me in Intellij)
Your build should automatically generate the open api classes , to refer the generated classes in your java project you should add the generated class path to your source directory like it was mentioned in the other answers
https://developer.android.com/studio/build/build-variants#configure-sourcesets
As far as the task dependency goes , in android tasks are generated after configuration thus for gradle to recognize the task , wrap it inside afterEvaluate block like
afterEvaluate {
tasks.compileDebugJavaWithJavac.dependsOn(tasks.openApiGenerate)
}
I had this issue, and this answer https://stackoverflow.com/a/55646891/14111809 led me to a more informative error:
error: incompatible types: Object cannot be converted to Annotation
#java.lang.Object()
Taking a look at the generated files that were causing this error, noticed:
import com.squareup.moshi.Json;
After including a Moshi in the app build.gradle, the build succeeded and the generated code was accessible.
implementation("com.squareup.moshi:moshi-kotlin:1.13.0")
TL;DR;
How to add two or more kotlin native modules on an iOS project without getting duplicate symbols error?
The detailed question
Let's assume a multi-module KMP project as a follow where there exists a native app for Android and a native app for iOS and two common modules to hold shared kotlin code.
.
├── android
│ └── app
├── common
│ ├── moduleA
│ └── moduleB
├── ios
│ └── app
The module A contains a data class HelloWorld and has no module dependencies:
package hello.world.modulea
data class HelloWorld(
val message: String
)
Module B contains an extension function for HelloWorld class so it depends on module A:
package hello.world.moduleb
import hello.world.modulea.HelloWorld
fun HelloWorld.egassem() = message.reversed()
The build.gradle configuration of the modules are:
Module A
apply plugin: "org.jetbrains.kotlin.multiplatform"
apply plugin: "org.jetbrains.kotlin.native.cocoapods"
…
kotlin {
targets {
jvm("android")
def iosClosure = {
binaries {
framework("moduleA")
}
}
if (System.getenv("SDK_NAME")?.startsWith("iphoneos")) {…}
}
cocoapods {…}
sourceSets {
commonMain.dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72"
}
androidMain.dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.3.72"
}
iosMain.dependencies {
}
}
}
Module B
apply plugin: "org.jetbrains.kotlin.multiplatform"
apply plugin: "org.jetbrains.kotlin.native.cocoapods"
…
kotlin {
targets {
jvm("android")
def iosClosure = {
binaries {
framework("moduleB")
}
}
if (System.getenv("SDK_NAME")?.startsWith("iphoneos")) {…}
}
cocoapods {…}
sourceSets {
commonMain.dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72"
implementation project(":common:moduleA")
}
androidMain.dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.3.72"
}
iosMain.dependencies {
}
}
}
It looks pretty straightforward and it even works on android if I configure the android build gradle dependencies as a following:
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72"
implementation project(":common:moduleA")
implementation project(":common:moduleB")
}
However, this does not seem to be the correct way to organize multi modules on iOS, because running the ./gradlew podspec I get a BUILD SUCCESSFUL as expected with the following pods:
pod 'moduleA', :path => '…/HelloWorld/common/moduleA'
pod 'moduleB', :path => '…/HelloWorld/common/moduleB'
Even running a pod install I get a success output Pod installation complete! There are 2 dependencies from the Podfile and 2 total pods installed. whats looks correctly once the Xcode shows the module A and module B on the Pods section.
However, if I try to build the iOS project I get the following error:
Ld …/Hello_World-…/Build/Products/Debug-iphonesimulator/Hello\ World.app/Hello\ World normal x86_64 (in target 'Hello World' from project 'Hello World')
cd …/HelloWorld/ios/app
…
duplicate symbol '_ktypew:kotlin.Any' in:
…/HelloWorld/common/moduleA/build/cocoapods/framework/moduleA.framework/moduleA(result.o)
…/HelloWorld/common/moduleB/build/cocoapods/framework/moduleB.framework/moduleB(result.o)
… a lot of duplicate symbol more …
duplicate symbol '_kfun:kotlin.throwOnFailure$stdlib#kotlin.Result<#STAR>.()' in:
…/HelloWorld/common/moduleA/build/cocoapods/framework/moduleA.framework/moduleA(result.o)
…/HelloWorld/common/moduleB/build/cocoapods/framework/moduleB.framework/moduleB(result.o)
ld: 9928 duplicate symbols for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
My knowledge in iOS is not that much, so to my untrained eyes, it looks like each module is adding its own version of the things instead of using some resolutions strategy to share it.
If I use only the module A the code works and run as expected, so I know the code itself is correct, the problem is how to manage more than 1 module, so that the question, how to add both (module A and module B) on iOS and make things works?
P.S
I did reduce the code as much as I could, trying to keep only the parts that I guess is the source of the problem, however, the complete code is available here if you want to check anything missing in the snippets, or if you want to run and try to solve the problem…
Multiple Kotlin frameworks can be tricky, but should be working as of 1.3.70 which I see you have.
The issue seems to be that both frameworks are static, which is currently an issue in 1.3.70 so it isn't working. (This should be updated by 1.40). It looks like by default the cocoapods plugin sets the frameworks to be static which won't work. I'm unaware of a way to change cocoapods to set it as dynamic but I've tested building without cocoapods and using the isStatic variable in a gradle task, and have gotten an iOS project to compile. Something like:
binaries {
framework("moduleA"){
isStatic = false
}
}
For now you can work around the issue using this method by using the code above and creating a task to build the frameworks(here's an example)
Another thing worth noting is that on the iOS side, the HelloWorld classes will appear as two separate classes despite both coming from moduleA. It's another strange situation with multiple Kotlin frameworks, but I think the extension will still work in this case since you're returning a string.
I actually just wrote up a blog post about multiple Kotlin frameworks that may help with some other questions if you'd like to take a look. https://touchlab.co/multiple-kotlin-frameworks-in-application/
EDIT: Looks like cocoapodsext also has an isStatic variable, so set it to isStatic = false
tl:dr You currently can't have more than one static Kotlin frameworks in the same iOS project. Set them to not be static using isStatic = false.
However, if I try to build the iOS project I get the following error:
This particular error is a known issue. Multiple debug static frameworks are incompatible with compiler caches.
So to workaround the issue you can either disable compiler caches by putting the following line into your gradle.properties:
kotlin.native.cacheKind=none
or make the frameworks dynamic by adding the following snippet to your Gradle build script:
kotlin {
targets.withType<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget> {
binaries.withType<org.jetbrains.kotlin.gradle.plugin.mpp.Framework> {
isStatic = false
}
}
}
See https://youtrack.jetbrains.com/issue/KT-42254 for more details.
I guess current behaviour for multiple frameworks doesn't make much sense for the original topic starter, I'm just putting my answer here for anyone who might encounter the same issue.
My knowledge in iOS is not that much, so to my untrained eyes, it looks like each module is adding its own version of the things instead of using some resolutions strategy to share it.
This is exactly how it is supposed to work at this moment. But "versions of the things" in each of the frameworks are put into the separate independent namespaces, so there should be no linkage errors, and the one you've encountered is a bug.
I'm writing a custom Gradle plugin which should be able to access the configuration parameters from the Android plugin.
I can do certainly do this from a groovy plugin (which is my current code):
MyPlugin.groovy:
class MyPlugin implements Plugin<Project> {
void apply(Project project) {
project.android.applicationVariants.all { variant ->
variant.registerJavaGeneratingTask( // all the right parameters
My problem is that I don't want the plugin to be in Groovy, I want it using Kotlin.
Simply typing project.android in Kotlin does not work, as far as I understand I would need to write something similar to what's below:
MyPlugin.kt
import com.android.build.gradle.AndroidBasePlugin
class MyPlugin : Plugin<Project> {
override fun apply(target: Project) {
val android = target.plugins.findPlugin(AndroidBasePlugin::class.java)
// and call android here
My main problem is that that import import com.android. does not exist.
I tried adding lots of different dependecies on build.gradle.kts, like example implementation("com.android.tools.build:gradle:3.6.1") but nothing gives me access to it.
questions:
is that the right approach?
case:
(yes) 2. How do I import and use it?
(no) 2. What is the right approach?
tl;dr:
how to write project.android.applicationVariants.all { variant -> inside a Gradle Kotlin plugin
edit:
#tim_yates answer worked perfectly. To whom is interested, here is the final code.
build.gradle.kts:
plugins {
`kotlin-dsl`
}
repositories {
mavenCentral()
jcenter()
google()
}
dependencies {
implementation("com.android.tools.build:gradle:3.6.1")
... my other dependencies
}
package-name/MyPlugin.kt:
import com.android.build.gradle.AppExtension
import com.android.build.gradle.api.ApplicationVariant
(...)
override fun apply(target: Project) {
val android = target.extensions.findByType(AppExtension::class.java)
android?.applicationVariants?.configureEach {
configureVariant(target, this)
}
private fun configureVariant(...) { // register my taks there
}
What you'll need is something like this:
project.extensions.configure<AppExtension>("android") {
it.applicationVariants.all {
it.registerJavaGeneratingTask( // all the right parameters
}
}
And you'll need the com.android.tools.build:gradle dependency as you say.
Why it's not being recognized, I'm not sure... Are you writing a standalone plugin? If so, what you have works... If it's an inline plugin in a buildSrc folder, then you'll need to add the lib to the buildSrc build
I created Kotlin native project to shared code between iOS and android.I did integration for cocoapods in-order to use in iOS project using POD file, Project successfully run on iOS and Android but when I tried to used iOS pod library in Kotlin native project, I start getting errors below.
I know that I have to run pod install first from Xcode in-order to compile library in Kotlin native project.
SO iOS pod library should be converted via cinterop, to use in Kotlin Native Project.
I run below command just to check either framework compile successfully or not.
./gradlew :SharedCode:packForXCode
got this error
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':SharedCode:cinteropAFNetworkingIos'.
> Cannot perform cinterop processing for AFNetworking: cannot determine headers location.
Probably the build is executed from command line.
Note that a Kotlin/Native module using CocoaPods dependencies can be built only from Xcode.
please find below Gradle file.
build.Gradle.kts
plugins {
kotlin("multiplatform")
kotlin("native.cocoapods")
}
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("Shared") {
baseName = "SharedCode"
}
}
}
jvm("android")
sourceSets["commonMain"].dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-common")
}
sourceSets["androidMain"].dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib")
}
version = "1.0.0"
cocoapods {
summary = "This is sample Summary"
homepage = "Home URL"
pod("AFNetworking", "~> 3.2.0")
}
}
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)```
I just tried these steps and managed to extend this sample with working interop of AFNetworking framework using CocoaPods plugin. These are my steps:
After cloning the project and checking out the step-008 branch, edit build.gradle.kts that way: add CocoaPods plugin to the plugins section as id("org.jetbrains.kotlin.native.cocoapods") and delete whole binaries block from the iOSTarget specification block. This should be done because the cocoaPods plugin creates framework on it's own, and we should avoid duplication (see discussion). Instead of that block, add cocoapods specification. This part of the script should look like that:
iOSTarget("ios") {}
version = "1.0.0"
cocoapods {
summary = "This is sample Summary"
homepage = "Home URL"
pod("AFNetworking", "~> 3.2.0")
}
Then, execute Gradle task podspec to generate podspec for your framework. This should be done inside of the /SharedCode/ directory, as the previous one. If the task is not presented, it means that the CocoaPods plugin was not applied correctly.
When the podspec is ready, we can use it from the Xcode project. To do it, open the /native/KotlinIOS/ directory, and create Podfile there. I used these contents for is:
use_frameworks!
platform :ios, '9.0'
target 'KotlinIOS' do
pod 'SharedCode', :path => '../../SharedCode'
end
The important part here is that the name corresponds to our framework name, and the relative path points on the place containing build.gradle.kts from the first step. After Podfile creation, install pods here using pod install from the terminal(CocoaPods should be installed).
Now open generated KotlinIOS.xcworkspace with Xcode. There, one more thing should be fixed. At this moment the KotlinIOS project is set to search for frameworks only in /SharedCode/build/Xcode-frameworks/ directory, but CocoaPods won't put anything there. So, select KotlinIOS on the left panel, open Build Settings tab and find there Search Paths -> Framework Search Paths. Press + and add $(inherited) to the list, to make available frameworks that CocoaPods installed.
Now, execute Build from the Xcode. After that, in your Kotlin IDE the AFNetworking package should become available. To import it, use import cocoapods.AFNetworking.*. For the first time it might need to invalidate Caches and Restart, to make it see this package correctly.
I hope this will help. Please comment if something is unclear in this instruction.
Adding this answer because some users are wondering why IDE suggestions are not coming in case of IOS common code. The reason is that still the commonization of the IOS code is not done which means iosMain won't work it will compile but no show suggestions or import. To make it work as usual you have to specify a specific source set.
for example.
kotlin {
android()
iosX64()
cocoapods {
// Configure fields required by CocoaPods.
summary = "Kotlin Multiplatform Firebase login sample"
homepage = "https://github.com/worstkiller/firebaseloginkmm"
pod("FirebaseAuth")
frameworkName = "sharedFramework"
}
sourceSets {
val commonMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.coroutines}")
}
}
val commonTest by getting {
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
}
}
val androidMain by getting {
dependencies {
implementation("com.google.firebase:firebase-auth:${Versions.firebase_auth_android}")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:${Versions.coroutines}")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:${Versions.viewmodel}")
}
}
val androidTest by getting {
dependencies {
implementation(kotlin("test-junit"))
implementation("junit:junit:${Versions.junit}")
}
}
val iosX64Main by getting
val iosX64Test by getting
configure(listOf(iosX64Main)) {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-native:${Versions.coroutines_native}")
}
}
}
}
in the above code block, I have added the support for only iosX64 source sets which means it will work on machine only. You can similarly add support for iosArm64.
Here is an issue I have already raised if possible vote it to make it count.
https://youtrack.jetbrains.com/issue/KT-42319