DEV Community

Cover image for Going Swiftly: Using a Swift-only libraries in your Kotlin Multiplatform App
Evgeny Khokhlov
Evgeny Khokhlov

Posted on • Edited on

Going Swiftly: Using a Swift-only libraries in your Kotlin Multiplatform App

This article demonstrates how to use the Apple CryptoKit in KMM shared module. You'll learn how to create a Kotlin-compatible wrapper in Swift, build it using Swift Klib Gradle Plugin, and provide bindings in KMM shared module through Kotlin/Native cinterop.

By using this technique, developers can access cryptographic algorithms (such as SHA, MD5, AES, ChaChaPoly, and more) in Kotlin Multiplatform without needing to rely on additional libraries. This is particularly beneficial since there are currently no widely-used and reliable cryptographic libraries for Kotlin Multiplatform from verified and respected companies.

Kotlin Multiplatform Mobile (KMM) is an SDK designed to simplify the development of cross-platform mobile applications. You can share common code between iOS and Android apps and write platform-specific code only where it's necessary

Kotlin Multiplatform brings a lot of benefits to the project, including increased code reuse, consistent business logic, and faster time-to-market. Additionally, one of its key advantages is the ability to access native APIs for each platform, enabling developers to tap into platform-specific functionality while still maintaining a shared codebase. Unfortunately, this powerful feature has certain limitations for the iOS platform. Kotlin does not have direct interoperability with the Swift language; instead, it relies on interoperability with Objective-C. As a result, there is no built-in support in Kotlin for Swift-only libraries that do not have an Objective-C API. For example, the Apple CryptoKit library cannot be directly accessed from Kotlin. Fortunately, a special Swift Klib Gradle Plugin exists to help overcome this limitation.

In this article, we'll explore how to use this plugin to build a simple Kotlin Multiplatform app that showcases the MD5 hash of a selected file.

The final code of the app can be found in the /examples folder of Swift Klib repository.

Table Of Contents

Getting Started

To get started, we'll create a Kotlin Multiplatform app using the KMM plugin in Android Studio.

If you run into any issues while creating your Kotlin Multiplatform app, be sure to check out the official KMM tutorial for additional guidance and support.

While this article will focus on the iOS implementation, if you're interested in exploring the Android implementation, you can check out our GitHub repository for more information.

Our first step will be to create a wrapper for the CryptoKit library that provides an Objective-C API for the MD5 hash function:

import CryptoKit
import Foundation

@objc public class KCrypto : NSObject {
    @objc(md5:) public class func md5(data: NSData) -> String {
        let hashed = Insecure.MD5.hash(data: data)
        return hashed.compactMap { String(format: "%02x", $0) }.joined()
    }
}
Enter fullscreen mode Exit fullscreen mode

By utilizing the @objc attribute, we can control which Objective-C API is generated from Swift code and how it will appear in Kotlin. You can find more information about this in the documentation.

It’s important that our class inherited from NSObject and has @objc declarations, that make our Swift code compatible with Objective-C. More about Swift → Objective-C compatibility you can read in the article.

Swift Klib

With the code for our wrapper in hand, we can now proceed to add it to the shared module, which is located in the native/KCrypto directory. Once we've done that, we're ready to set up the Swift Klib Gradle plugin. To add the plugin, simply include it in the plugins section of the shared module's build.gradle.kts file. Here's what the section should look like after adding the plugin:

plugins {
    kotlin("multiplatform")
    id("io.github.ttypic.swiftklib") version "0.2.1"
    //...
}
Enter fullscreen mode Exit fullscreen mode

After adding the Swift Klib plugin, the next step is to add cinterop for the target platforms in the Kotlin Multiplatform Plugin. The good news is that there's no need to configure it or add a .def file, as all the necessary configuration will be handled automatically by the Swift Klib plugin.

kotlin {
    listOf(
        iosX64(),
        iosArm64(),
        iosSimulatorArm64(),
    ).forEach {
        it.compilations {
            val main by getting {
                cinterops {
                    create("KCrypto")
                }
            }
        }
    }

    //...
}
Enter fullscreen mode Exit fullscreen mode

Next, we need to provide the necessary settings for the Swift Klib plugin in the swiftklib extension. This involves specifying the path and packageName parameters, which will be used by the plugin to generate the necessary bindings.

Here's an example of what the extension configuration might look like:

swiftklib {
    create("KCrypto") {
        path = file("native/KCrypto")
        packageName("com.ttypic.objclibs.kcrypto")
    }
}
Enter fullscreen mode Exit fullscreen mode

In this case, we've specified that the Swift library files are located in the native/KCrypto directory and that the generated package name should be com.ttypic.objclibs.kcrypto. Be sure to adjust these settings to match the specifics of your project.

KMM Shared Module

With the Swift Klib plugin properly configured, we can now access our wrapper for the CryptoKit library in the KMM shared module:

import com.ttypic.objclibs.kcrypto.KCrypto

object FileMd5Hasher {
    fun hash(nsdata: NSData): String {
        return KCrypto.md5(data = nsdata)
    }
}
Enter fullscreen mode Exit fullscreen mode

Now we can use this FileMd5Hasher both in KMM shared module and our iOS app module.

Using in SwiftUI

The final step is to use our FileMd5Hasher to calculate the MD5 hash of a selected file in a SwiftUI app. Here's an example of how we might use FileMd5Hasher to achieve this:

struct ContentView: View {
    @State var fileMd5 = ""
    @State var fileImporterVisible = false

    var body: some View {
        VStack(spacing: 10) {
            if fileMd5 != "" { Text("File's MD5 hash:").font(.title) }
            if fileMd5 != "" { Text(fileMd5) }
            Button(action: {
                fileImporterVisible.toggle()
            }) {
               Text("Choose File")
            }
        }.fileImporter(
             isPresented: $fileImporterVisible,
             allowedContentTypes: [.pdf],
             allowsMultipleSelection: false
         ) { result in
             do {
                 guard let selectedFile: URL = try result.get().first else { return }
                 guard let data = try? Data(contentsOf: selectedFile) else { return }
                 fileMd5 = FileMd5Hasher.shared.hash(nsdata: data)
             } catch {
                 // Handle errors
             }
         }
    }
}
Enter fullscreen mode Exit fullscreen mode

While Kotlin has no direct interoperability with Swift, the Swift Klib Gradle plugin provides a solution for accessing Swift-only APIs in Kotlin Multiplatform shared modules. In this article, we explored how to use the Swift Klib plugin to create a wrapper for the CryptoKit library and calculate the MD5 hash of a selected file in a KMM app.

By following the steps outlined in this article, developers can easily integrate Swift-only APIs into their Kotlin Multiplatform projects, and take full advantage of the unique features and functionality provided by both languages.

Resources

  1. Swift Klib GitHub
  2. Compatible with Objective-C Swift Code
  3. Official KMM tutorial
  4. How to use CryptoKit

Top comments (4)

Collapse
 
softartdev profile image
Artur Babichev

Can the plugin be used with third party libraries distributed via CocoaPods? In your example, CryptoKit is Apple's built-in framework. But what if I need access to third party libraries (such as Kronos for example).

Collapse
 
ttypic profile image
Evgeny Khokhlov

Unfortunately it is not possible for now, plugin only works with Apple's built-in frameworks. But I am thinking about ability to add dependencies. Maybe I'll add this functionality later, I am still thinking about right design for it. Stay tuned, I will post about new features.

Collapse
 
ttypic profile image
Evgeny Khokhlov

Also if you need specifically Kronos, I guess as workaround you can connect Kronos source code as git submodule (or just copy source code to your repository) add Objective-C adapter and use plugin to build it. If you face any troubles, I will be happy to assist.

Collapse
 
ashikgeidea profile image
AshikGeidea

Please check it is possible to do with .def and Objective-C API defined in the header.
medium.com/kodein-koders/create-a-...