9

I'm trying to call Swift/Objective-C code from Kotlin in a multiplatform project. There are no problems with calls to platform code. But when I'm trying to call some library (or framework, not sure how it is properly called as I'm not an iOS dev) it fails. Docs states that it is possible to call Objective-C code and Swift if it is properly exported:

Kotlin/Native provides bidirectional interoperability with Objective-C. Objective-C frameworks and libraries can be used in Kotlin code if properly imported to the build (system frameworks are imported by default). See e.g. "Using cinterop" in Gradle plugin documentation. A Swift library can be used in Kotlin code if its API is exported to Objective-C with @objc. Pure Swift modules are not yet supported.

But it does not say anything about how can I import them properly. It only point to gradle plugin description that describes old version of gradle plugin. So it does not work for me. Finally I figured out something might be the way to import Objective-C code:

fromPreset(presets.iosX64, 'ios') {
        compilations.main.outputKinds('FRAMEWORK')
        compilations.main {
            cinterops {
                firebase {
                    def pods = '${System.getProperty("user.home")}/Projects/kmpp/iosApp/Pods/'
                    includeDirs '${pods}Firebase/CoreOnly/Sources',
                            '${pods}FirebaseAnalytics/Frameworks/FirebaseAnalytics.framework/Headers'
                }
            }
        }
    }

Build runs without failures, but it does not import anything. What am I doing wrong? Is it possible to import such a lib at all?

UPD:

here I found an example of usage cinterop tool like this:

cd samples/gitchurn
../../dist/bin/cinterop -def src/main/c_interop/libgit2.def \
 -compilerOpts -I/usr/local/include -o libgit2

It looks like cinterop tool should be in /dist/bin/ folder in my projects but there is no such folder. Where do I get cinterop tool ?

Mark
  • 7,167
  • 4
  • 44
  • 68
oleg.semen
  • 2,901
  • 2
  • 28
  • 56

2 Answers2

7

I ended up with this cinterops section in build.gradle

    fromPreset(presets.iosX64, 'ios') {
        // This preset is for iPhone emulator
        // Switch here to presets.iosArm64 (or iosArm32) to build library for iPhone device
        compilations.main {
            outputKinds('FRAMEWORK')
            cinterops {
                firebase {
                    defFile "$projectDir/src/iosMain/cinterop/firebase.def"
                    includeDirs {
                        allHeaders "$projectDir/../iosApp/Pods/FirebaseCore/Firebase/Core/Public",
                                   "$projectDir/../iosApp/Pods/FirebaseDatabase/Firebase/Database/Public"
                    }

                    compilerOpts "-F$projectDir/../iosApp/Pods/Firebase -F$projectDir/../iosApp/Pods/FirebaseCore -F$projectDir/../iosApp/Pods/FirebaseDatabase"
                    linkerOpts "-F$projectDir/../iosApp/Pods/Firebase -F$projectDir/../iosApp/Pods/FirebaseCore -F$projectDir/../iosApp/Pods/FirebaseDatabase"
                }
            }
        }
    }

end this .def file:

language = Objective-C
headers = FirebaseCore.h FirebaseDatabase.h

What's going on here? Cocopods frameworks are placed in Pods directory in your Xcode project. Navigating this folder a bit, you'll find what you need. I'm not sure if there is some standard but firebase place main header file in Public folder. And it contains references to other header files it needs... So you specify these file names in your .def file in the headers section.

Next, you need to specify where to look for these files and others referenced by them. You can do it in the .def file in includeDirs or in build.gradle file. I prefer to use the build.gradle file as it can use variables. So you specify the path to these Public folders. (This is enough for kotlin to see library API, but in order to be able to run the app you need to compile and link this library...)

Then the compiler and linker need to know where library/framework is. So you specify path to root folder of the framework in compilerOpts and linkerOpts prefixing them with -F if it is a framework or -L if it is a library.

donjuedo
  • 2,475
  • 18
  • 28
oleg.semen
  • 2,901
  • 2
  • 28
  • 56
  • 1
    Consuming objectiv-c static methods directly from kotlin native is leading me to this error **Undefined symbols for architecture x86_64**. Examples: `FIRAnalytics.logEventWithName("value", null)` or `FIRApp.configure()` or `FIRDatabase.database()` Running the above examples from the iOS actual kotlin classes will fail with **Undefined symbols for architecture x86_64** Have you faced this error? How can I call "cinteroped" objective-c static methods from kotlin? – João Zão Mar 19 '19 at 14:45
  • I'm getting the same error as @JoãoZão. Does anyone have a solution? – Thomas Vos Mar 22 '19 at 16:18
  • @JoãoZão did you add `FirebaseAnalytics` to your `.def` file, `includeDirs` and compiler/linker Opts ? In my example Database only is available... – oleg.semen Mar 26 '19 at 11:34
  • Firebase consists of many parts, you have to specify each one you want to use. – oleg.semen Mar 26 '19 at 11:34
  • That happens with `FIRDatabase.database()`.. I found that in the linkerOpts we need to pass the Path to the framework through the `-F` option(like you are doing) and we also have to pass the framework file itself through `-framework` option Example with def file: `language = Objective-C` `headers = FirebaseCore.h FirebaseDatabase.h` `linkerOpts = /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/10.0.0/lib/darwin/libclang_rt.osx.a -F ${fullpath-to-frameworks-dir} -framework FirebaseDatabase` Sorry for the bad format. – João Zão Mar 26 '19 at 13:54
  • @JoãoZão you probably made some mistake in gradle file but done it right in `.def` file. So that's why it works. As Nikolay Igotti mentioned in another my question `Linker options from .def and Gradle pluginare concatenated essentially.` see last comment https://stackoverflow.com/questions/54047711/kotlin-native-interop-linker-could-not-find-framework?noredirect=1#comment97505184_54047711 – oleg.semen Mar 28 '19 at 13:25
  • That is correct, I know that. I just gave the def file example since it is less verbose than the equivalent in the gradle file. – João Zão Mar 28 '19 at 13:52
  • I see. In fact gradle file might be less verbose as you can use variables there. – oleg.semen Mar 28 '19 at 15:40
  • Ok I didn't want to mean verbose, I was actually referring to indentation and how it seemed to be easier to represent the .def file instead of the gradle equivalent here in comments. Anyway this is pointless to the question itself :) and BTW thanks for your answer as it was a really nice starting point for me. – João Zão Mar 29 '19 at 10:32
  • You are Wellcome – oleg.semen Mar 29 '19 at 19:19
  • Hello @oleg.semen. Could you, please, share the sample project with Firebase connected to Kotlin Native from CocoaPods? I have tried the project from https://github.com/RubyLichtenstein/Kotlin-Multiplatform-Firebase, but linking fails with an error: _ld: framework not found FirebaseFirestore_. I have already tried different combinations of placing linkerOpts in .def file and in build.gradle, but nothing works for me. Perhaps sample of build.gradle and .def file which has worked for you would be enough. Thank you in advance. – Olexandr Stepanov Apr 24 '19 at 15:44
  • hi @OlexandrStepanov, it's dangerous to share firebase token, so here is my project but you have to register a project in firebase, download json and plist files and add them to the projects. https://github.com/semenoh/SlicedButWhole?files=1 – oleg.semen Apr 26 '19 at 19:31
  • also you can watch a video of my talk at local kotlin usergroup https://youtu.be/hA9M92TZTU8 – oleg.semen Apr 26 '19 at 19:33
  • Hello @oleg.semen, thank you very much for sharing this project. I believe my fail from the same approach was because I didn't add linking to the pods of the `app` framework target in Xcode. That is crucial. – Olexandr Stepanov Apr 29 '19 at 11:21
0

It looks like you are going to use a cocoapods library. Currently the Gradle plugin has no support for cocapods out of the box. But may be a dependency on your library can be configured "manully". Could you please share a link to your project?

Ilya Matveev
  • 191
  • 4
  • yes, that's cocoapods. There is nothing special in my project so I did not push it to github. It is just a default multiplatform project created by IDEA where I added firebase lib as described here: https://firebase.google.com/docs/ios/setup If gradle plugin does not support cocoapods out of the box is it possible to do it manually ? For example by creating .def file and running cinterop tool ? BTW I've been trying to do this as well and it did not work. My terminal does not recognize cinterop command. – oleg.semen Jan 03 '19 at 15:35
  • I guess yes, it can be done manualy but I cannot check it right now. I'll check it when it's possible and answer you. – Ilya Matveev Jan 04 '19 at 06:33