Search code examples
google-cloud-firestorecocoapodskotlin-multiplatformfirebase-dynamic-linksxcode-previews

KMP - How to get XCode preview working in KMP with FirebaseFirestore using CocoaPods?


I am trying to build Xcode preview in kotlin multiplatform project when using FirebaseFirestore with CocoaPods.

With the following gradle files everything works fine, app runs & preview runs.

Module build.gradle.kts:

plugins {
    //trick: for the same plugin versions in all sub-modules
    alias(libs.plugins.androidApplication).apply(false)
    alias(libs.plugins.androidLibrary).apply(false)
    alias(libs.plugins.kotlinAndroid).apply(false)
    alias(libs.plugins.kotlinMultiplatform).apply(false)
    alias(libs.plugins.kotlinCocoapods).apply(false)

    id("com.google.gms.google-services") version "4.4.0" apply false
}

buildscript {
    dependencies {
        classpath("dev.icerock.moko:resources-generator:0.23.0")
    }
}

gradle.properties:

#Gradle
org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M"
org.gradle.caching=true
org.gradle.configuration-cache=true

#Kotlin
kotlin.code.style=official
#Is forced by Moko Resources library (specificaly by "androidMain.dependsOn(...)")
kotlin.mpp.applyDefaultHierarchyTemplate=false 

#Android
android.useAndroidX=true
android.nonTransitiveRClass=true

Shared build.gradle.kts:

plugins {
    alias(libs.plugins.kotlinMultiplatform)
    alias(libs.plugins.kotlinCocoapods)
    alias(libs.plugins.androidLibrary)

    id("com.google.devtools.ksp") version "1.9.20-1.0.14"
    id("com.rickclephas.kmp.nativecoroutines") version "1.0.0-ALPHA-21"

    kotlin("plugin.serialization") version "1.9.20"

    id("dev.icerock.mobile.multiplatform-resources")
}

kotlin {
    androidTarget {
        compilations.all {
            kotlinOptions {
                jvmTarget = "11"
            }
        }
    }
    iosX64()
    iosArm64()
    iosSimulatorArm64()

    jvmToolchain {
        languageVersion.set(JavaLanguageVersion.of("11"))
    }

    cocoapods {
        summary = "Some description for the Shared Module"
        homepage = "Link to the Shared Module homepage"
        version = "1.0"
        ios.deploymentTarget = "16.0"
        podfile = project.file("../iosApp/Podfile")
        framework {
            baseName = "shared"
            isStatic = false

            export("dev.icerock.moko:resources:0.23.0")
            export("dev.icerock.moko:graphics:0.9.0")
        }

        pod("FirebaseCore", linkOnly = true)
        pod("FirebaseAuth", linkOnly = true)
        //pod("FirebaseCoreExtension", linkOnly = true)
        //pod("FirebaseSharedSwift", linkOnly = true)
        //pod("FirebaseFirestore", linkOnly = true)
    }
    
    sourceSets {
        sourceSets.all {
            languageSettings.optIn("kotlin.experimental.ExperimentalObjCName")
            languageSettings.optIn("kotlinx.cinterop.ExperimentalForeignApi")
        }
        androidMain {
            dependsOn(commonMain.get()) // Moko Resources needs this dependency
        }
        // Implicitly specifies dependencies because Moko Resources disabled the default hierarchy template via "androidMain.dependsOn(...)" above
        val iosX64Main by getting
        val iosArm64Main by getting
        val iosSimulatorArm64Main by getting
        val iosMain by creating {
            dependsOn(commonMain.get())
            iosX64Main.dependsOn(this)
            iosArm64Main.dependsOn(this)
            iosSimulatorArm64Main.dependsOn(this)
        }
        commonMain.dependencies {
            api("com.rickclephas.kmm:kmm-viewmodel-core:1.0.0-ALPHA-15")

            implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")

            api("dev.icerock.moko:resources:0.23.0")
            api("dev.icerock.moko:resources-compose:0.23.0")

            api("org.lighthousegames:logging:1.3.0")

            implementation("dev.gitlive:firebase-auth:1.10.4")
            //implementation("dev.gitlive:firebase-firestore:1.10.4")
        }

        commonTest.dependencies {
            implementation(libs.kotlin.test)

            implementation("dev.icerock.moko:resources-test:0.23.0")
        }
    }
}

android {
    namespace = "com.example.project"
    compileSdk = 34
    defaultConfig {
        minSdk = 24
    }
}

multiplatformResources {
    multiplatformResourcesPackage = "com.example.project"
    iosBaseLocalizationRegion = "en"
}

// This resolves Moko Resources issue where the build order could be wrong
tasks.configureEach {
    if (name == "generateMRcommonMain") {
        dependsOn("kspKotlinIosX64")
        dependsOn("kspKotlinIosArm64")
        dependsOn("kspKotlinIosSimulatorArm64")
    }
    if (name == "generateMRiosX64Main") {
        dependsOn("kspKotlinIosX64")
    }
    if (name == "generateMRiosArm64Main") {
        dependsOn("kspKotlinIosArm64")
    }
    if (name == "generateMRiosSimulatorArm64Main") {
        dependsOn("kspKotlinIosSimulatorArm64")
    }
}

Podfile:

target 'iosApp' do
  use_frameworks!
  platform :ios, '16.0'
  pod 'shared', :path => '../shared'
  
  pod 'KMPNativeCoroutinesCombine', '1.0.0-ALPHA-21'
  
  pod 'KMMViewModelSwiftUI', '1.0.0-ALPHA-15'

  # Required by shared firebase dependencies
  pod 'FirebaseAuth'
  pod 'FirebaseFirestore'
end

But if i uncomment the Firestore lines:

pod("FirebaseCoreExtension", linkOnly = true)
pod("FirebaseSharedSwift", linkOnly = true)
pod("FirebaseFirestore", linkOnly = true)

and

implementation("dev.gitlive:firebase-firestore:1.10.4")

Then even the app now fails to build with an error:

ld: Undefined symbols:
    _FIRFirestoreErrorDomain, referenced from:
        _cocoapods_FirebaseFirestore_FIRFirestoreErrorDomain_getter_wrapper0 in libdev.gitlive:firebase-firestore-cinterop-FirebaseFirestore-cache.a[2](libdev.gitlive:firebase-firestore-cinterop-FirebaseFirestore-cache.a.o)
    _OBJC_CLASS_$_FIRDocumentChange, referenced from:
        in libdev.gitlive:firebase-firestore-cache.a[2](libdev.gitlive:firebase-firestore-cache.a.o)
    _OBJC_CLASS_$_FIRDocumentReference, referenced from:
        in libdev.gitlive:firebase-firestore-cache.a[2](libdev.gitlive:firebase-firestore-cache.a.o)
    _OBJC_CLASS_$_FIRDocumentSnapshot, referenced from:
        in libdev.gitlive:firebase-firestore-cache.a[2](libdev.gitlive:firebase-firestore-cache.a.o)
    _OBJC_CLASS_$_FIRFieldPath, referenced from:
        in libdev.gitlive:firebase-firestore-cache.a[2](libdev.gitlive:firebase-firestore-cache.a.o)
    _OBJC_CLASS_$_FIRFieldValue, referenced from:
        in libdev.gitlive:firebase-firestore-cache.a[2](libdev.gitlive:firebase-firestore-cache.a.o)
    _OBJC_CLASS_$_FIRFirestore, referenced from:
        in libdev.gitlive:firebase-firestore-cache.a[2](libdev.gitlive:firebase-firestore-cache.a.o)
    _OBJC_CLASS_$_FIRGeoPoint, referenced from:
        in libdev.gitlive:firebase-firestore-cache.a[2](libdev.gitlive:firebase-firestore-cache.a.o)
    _OBJC_CLASS_$_FIRQuerySnapshot, referenced from:
        in libdev.gitlive:firebase-firestore-cache.a[2](libdev.gitlive:firebase-firestore-cache.a.o)
    _OBJC_CLASS_$_FIRTimestamp, referenced from:
        in libdev.gitlive:firebase-firestore-cache.a[2](libdev.gitlive:firebase-firestore-cache.a.o)

Note that I am using a dynamic framework for preview to work and link only pods to solve missing/duplicate references.

I am not using FirebaseFirestore outside of commonMain and do not intend to.


Solution

  • I FINALLY figured it out. The problem is with the version of the firebase pods. Kotlin firebase library is not compatible with latest swift firebase library, although I did not find it anywhere specified, but it just does not work.

    The swift version 10.12 is working with current latest kotlin version 1.10.4.

    So I just specified the versions of pods and increased the java heap size.

    Shared build.gradle.kts:

    ...
    kotlin {
        ...
    
        cocoapods {
            summary = "Some description for the Shared Module"
            homepage = "Link to the Shared Module homepage"
            version = "1.0"
            ios.deploymentTarget = "16.0"
            podfile = project.file("../iosApp/Podfile")
            framework {
                baseName = "shared"
                isStatic = false
    
                ...
            }
    
            pod("FirebaseAuth") {
                version = "10.12"
                linkOnly = true
            }
            pod("FirebaseFirestore") {
                version = "10.12"
                linkOnly = true
            }
        }
        
        sourceSets {
            ...
            commonMain.dependencies {
                ...
    
                implementation("dev.gitlive:firebase-auth:1.10.4")
                implementation("dev.gitlive:firebase-firestore:1.10.4")
            }
            ...
        }
    }
    ...
    

    Podfile:

      ...
      # Required by shared firebase dependencies
      pod 'FirebaseAuth', '10.12'
      pod 'FirebaseFirestore', '10.12'
    end
    

    gradle.properties:

    ...
    org.gradle.jvmargs=-Xmx4096M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx4096M"
    ...
    

    There is no longer a need to explicitly add dependencies of pods, like FirebaseCore, etc.

    I figured it out thanks to an issue thread on kotlin library github. It is only very remotely related, but it might help.