Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Call Swift code from Kotlin Multiplatform

I have a simple swift class:

@objc public class Wallet: NSObject {
    @objc public func getPrivateKey() -> String {
       return ""
    }
}

that i would like to call from Kotlin. I have also created a bridging header that looks like this:

#ifndef iosApp_Bridging_Header_h
#define iosApp_Bridging_Header_h

#import "iosApp-Swift.h"

#endif /* iosApp_Bridging_Header_h */

and linked it in Objective-C Generated Interface Header Name

In Kotlin Multiplatform project I have defined expect class in common module of the project as follows:

expect class WalletMP() {
    fun getPrivateKey() : String
}

and in iosMain module i have defined actual class like this:

actual class WalletMP actual constructor() {
    actual fun getPrivateKey(): String {
        return ""
    }
}

Now, as far as I understand I should be able to call swift wallet class by just defining:

val wallet = Wallet()

but the swift class is not visible/accessible in Kotlin. Even if I define bridging header like so:

#ifndef iosApp_Bridging_Header_h
#define iosApp_Bridging_Header_h

#import <Foundation/Foundation.h>

@interface Wallet: NSObject
   -(NSString*)getPrivateKey;
@end

#endif /* iosApp_Bridging_Header_h */

The problem is the same. What am I missing? Did I forget to set something? I would really appreciate any kind of help.

like image 255
Alen Prošič Avatar asked Jun 04 '26 04:06

Alen Prošič


2 Answers

By default Kotlin/Native only provides wrappers for built-in iOS libraries (e.g. UIKit, Foundation, etc.). They are located in platform.* (e.g. import platform.UIKit.UIDevice)

But there are several options to use your own Objective-C (Swift with Objective-C API) code in your Kotlin Multiplatform app.

  1. If you are using Cocoapods gradle plugin you can create pod locally and cocoa plugin will provide Kotlin wrappers for you. By default you can import your Objective-C API from cocoapods.<POD_NAME> (or you can specify your own packageName). Take a look at this example.
  2. Compile your code as static library and use cinterop. You can also use SwiftKlib gradle plugin that will built and inject cinterop's for you.
  3. Another option, that I highly recommend will be to use DI container, you can create simple DI yourself or pick existing, e.g. Koin. In that case instead of expect/actual approach, you can create common interface:
interface Wallet() {
    fun getPrivateKey(): String
}

then implement this interface in iOS and Android apps and set it in DI, example for iOS:

class IosWallet: Wallet {
  // class definition goes here
}


@main
struct iOSApp: App {
    
    // DI init Call
    init() {
        DI.init(IosWallet())
    }
    
    var body: some Scene {
        //...
    }
}

like image 112
Evgeny K Avatar answered Jun 06 '26 17:06

Evgeny K


The approach I use (which leverages no 3rd party plugin or anything) is to write lazily initialized global properties which are initialized from the Swift side, just to be used in the shared module. Here's an example:

//Assuming this file is called iOSUtils.Kt (and it is in your shared module)
//Meaning that this could be in commonMain or iosMain
lateinit var aPureSwiftFunction: () -> Unit

fun someFunctionThatNeedsSwift() {
     aPureSwiftFunction()
}

On the Swift side, you just have to initialize it when your app launches, preferably in your app's struct's init block:

@main
struct iOSApp: App {
    init() {
        IOSUtilsKt.aPureSwiftFunction = {
             //do swift stuff
        }
    }
}

In terms of boiletplate code, it's the same as writing obj-C headers quantity-wise.

like image 42
Yurowitz Avatar answered Jun 06 '26 17:06

Yurowitz



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!