Search code examples
kotlinkotlin-multiplatformcarthagexcframeworksnowplow

Resolving third party cocoapod dependencies in Kotlin MPP


I am trying to setup a tracking library written in Kotlin Multiplatform to support all our mobile clients.

Tests for Android went well (integrating snowplow via gradle).

I also managed to integrate Snowplow via cocoapods into the MPP.

kotlin {
    ...
    cocoapods {
        ...
        pod("SnowplowTracker") {
            version = "~> 1.3.0"
        }
    }

And wrote following class in the X64 sourceset:

import cocoapods.SnowplowTracker.*
import com.tracking.domain.model.*

class X64Tracker {

    private val tracker: SPTracker

    init {
        val emitter = SPEmitter.build {
            it?.setUrlEndpoint("our.staging.endpoint")
            it?.setProtocol(SPProtocol.SPProtocolHttps)
        }

        tracker = SPTracker.build {
            emitter?.let { spEmitter -> it?.setEmitter(spEmitter) }
            it?.setBase64Encoded(false)
            it?.setSubject(SPSubject(platformContext = true, andGeoContext = true))
            it?.setAppId("MPP.test")
            it?.setApplicationContext(true)
        }
    }

    fun trackSomething() {
        track(
            eventData = getEventData(
                name = "MPP_test_event",
                appArea = EventArea.Lifecycle,
                action = EventAction.Check,
                objectType = EventObjectType.Device,
                source = EventSource.Client,
                screenName = EventScreenName.Congratulations,
            ), contexts = emptyList()
        )
    }

    private fun track(eventData: SPSelfDescribingJson, contexts: List<SPSelfDescribingJson?>) {
        try {
            val yo = SPSelfDescribing.build {
                it?.setEventData(eventData)
                it?.setContexts(contexts.toMutableList())
            }

            tracker.track(yo)
        } catch (e: IllegalStateException) {
            print("snowplow was not yet initialized when the following event occurred: $eventData")
        }
    }

    private fun getEventData(
        name: String,
        appArea: EventArea,
        action: EventAction,
        objectType: EventObjectType,
        source: EventSource,
        screenName: EventScreenName,
    ) = SPSelfDescribingJson(
        SCHEMA_EVENT, mapOf(
            PROPERTY_EVENT_NAME to name,
            PROPERTY_APP_AREA to appArea.serializedName,
            PROPERTY_ACTION to action.serializedName,
            PROPERTY_OBJECT_TYPE to objectType.serializedName,
            PROPERTY_SOURCE to source.serializedName,
            PROPERTY_SCREEN_NAME to screenName.serializedName,
        )
    )

}

Which is compiling and building our .framework files fine. Here is how we do that:

tasks.register<org.jetbrains.kotlin.gradle.tasks.FatFrameworkTask>("debugFatFramework") {
        baseName = frameworkName + "_sim"
        group = "Universal framework"
        description = "Builds a universal (fat) debug framework"
    
        from(iosX64.binaries.getFramework("DEBUG"))
    }
    
    tasks.register<org.jetbrains.kotlin.gradle.tasks.FatFrameworkTask>("releaseFatFramework") {
        baseName = frameworkName + "_arm"
        group = "Universal framework"
        description = "Builds a universal (release) debug framework"
    
        from(iosArm64.binaries.getFramework("RELEASE"))
    }

Afterwards we combine this into an .xcframework file using following command:

xcrun xcodebuild -create-xcframework \
    -framework tracking_arm.framework \
    -framework tracking_sim.framework \
    -output tracking.xcframework

We use Carthage to integrate it into our main app, but as soon I try to build the iOS project following error pops up:

Undefined symbols for architecture x86_64:
  "_OBJC_CLASS_$_SPSelfDescribing", referenced from:
      objc-class-ref in tracking_sim(result.o)
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

The weird thing: No matter which version of Snowplow I define in the cocoapods block - the syntax in my class does not need to change. Even updating to Snowplow 2.x doesn't require me to get rid of the SP prefixes.

I am not sure how to continue at all and appreciate any help.


Solution

  • The following:

    Undefined symbols for architecture x86_64:
      "_OBJC_CLASS_$_SPSelfDescribing", referenced from:
          objc-class-ref in kaiaTracking_sim(result.o)
    ld: symbol(s) not found for architecture x86_64
    

    That says the linker can't find SPSelfDescribing. I assume SPSelfDescribing is part of SnowplowTracker. You'll need to add SnowplowTracker to the iOS app.

    If you were using Cocoapods to integrate Kotlin into your iOS project, the podspec generated would include SnowplowTracker as a dependency.

    Since you are not using Cocoapods, you need to include it yourself. We recently had to figure out Carthage for a client. I would highly recommend moving to SPM or Cocoapods for a number of reasons, but that's a different discussion. To add SnowplowTracker with Carthage, add this to your Cartfile:

    github "snowplow/snowplow-objc-tracker" ~> 1.3
    

    Then when you update Carthage, add that to your iOS project. The linker will be able to find it.

    To be clear, the Undefined symbols for architecture x86_64 error isn't complaining about Kotlin. It's saying it can't find SnowplowTracker, which you need to add to the iOS project.