Hi all Is there any way to modularize a KMM projec...
# multiplatform
h
Hi all Is there any way to modularize a KMM project? I want to follow the clean architecture so in the bussiness layer I want to have 2 KMM modules. One for the data layer and the other one for the domain layer
b
Kmp modules are no different to any other gradle module. Only caveat for kmm in particular is that you can only have one kotlin native build for objc/swift integration, but that's easy to circumvent via wrapper module that pools in all required kmp dependencies
a
@Big Chungus can you give a little more explanation on
you can only have one kotlin native build for objc/swift integration, but that's easy to circumvent via wrapper module that pools in all required kmp dependencies
b
You cannot import 2 objc libs built from kotlin into objc/swift app.
So to make all your kmp modules available to ios code you need a wrapper module that would add other modules as dependencies and export them together via a single objc lib
a
Let's say the
shared
module has dependency on
domain
and
data
module will that work??
b
Sure, it's just a regular gradle module dependency. The limitation only applies when linking kmp code to ios side
h
Thanks for your explanation I’ve created another KMM module and named it data. Also I’ve add it to the shared module’s gradle like this:
Copy code
val commonMain by getting {
    dependencies {
        api(project(":data"))
    }
}
But in iOS side it cannot recognize the data module. There is no issue in Android. What is wrong with this implementation?
b
does your
data
module declare ios targets too? Did you try building it to see if it's just an IDE issue? Did you try syncing the project in IDE?
h
Yes the
data
module declares iOS targets (It is just a regular KMM module) I tried to clean and build the project but the issue is still there.
b
So does it build or not in gradle? I'm confused
It would help a lot if you could share your buildfiles for both modules.
h
There is a class in the
data
module. In Android when I use this class there is no problem and the project builds successfully but in iOS it says it cannot find that class. There is no issue in gradle
a
I think you have to export the
data
module under the
shared
module
b
Yes, I thought that was clear by now 😀 In other words, your swift code should only have "shared" module as dependency and access stuff in "data" via "shared" module (since you add it as dependency to that kmp module)
So to recap your current issue: you can build the project, but IDE is still showing "data" references in "shared" as missing/red?
h
@Big Chungus I just can build the Android project but in iOS it fails and says cannot find that class
b
Does "./gradlew clean build" report any errors for you?
h
No there is no error in gradle
b
Are you able to send "kotlin {}" blocks from your module buildfiles?
Also, is your ios issue reported on kotlin or swift code?
h
message has been deleted
You mean I send you the gradle of the modules? This is for
data
Copy code
kotlin {
    android()
    iosX64()
    iosArm64()
    iosSimulatorArm64()

    cocoapods {
        summary = "Some description for the Shared Module"
        homepage = "Link to the Shared Module homepage"
        version = "1.0"
        ios.deploymentTarget = "14.1"
        framework {
            baseName = "data"
        }
    }
    
    sourceSets {
        val commonMain by getting
        val commonTest by getting {
            dependencies {
                implementation(kotlin("test"))
            }
        }
        val androidMain by getting
        val androidTest by getting
        val iosX64Main by getting
        val iosArm64Main by getting
        val iosSimulatorArm64Main by getting
        val iosMain by creating {
            dependsOn(commonMain)
            iosX64Main.dependsOn(this)
            iosArm64Main.dependsOn(this)
            iosSimulatorArm64Main.dependsOn(this)
        }
        val iosX64Test by getting
        val iosArm64Test by getting
        val iosSimulatorArm64Test by getting
        val iosTest by creating {
            dependsOn(commonTest)
            iosX64Test.dependsOn(this)
            iosArm64Test.dependsOn(this)
            iosSimulatorArm64Test.dependsOn(this)
        }
    }
}
And this one is for
shared
Copy code
kotlin {
    android()
    iosX64()
    iosArm64()
    iosSimulatorArm64()

    cocoapods {
        summary = "Some description for the Shared Module"
        homepage = "Link to the Shared Module homepage"
        version = "1.0"
        ios.deploymentTarget = "14.1"
        podfile = project.file("../iosApp/Podfile")
        framework {
            baseName = "shared"
        }
    }
    
    sourceSets {
        val commonMain by getting {
            dependencies {
                api(project(":data"))
            }
        }
        val commonTest by getting {
            dependencies {
                implementation(kotlin("test"))
            }
        }
        val androidMain by getting
        val androidTest by getting
        val iosX64Main by getting
        val iosArm64Main by getting
        val iosSimulatorArm64Main by getting
        val iosMain by creating {
            dependsOn(commonMain)
            iosX64Main.dependsOn(this)
            iosArm64Main.dependsOn(this)
            iosSimulatorArm64Main.dependsOn(this)
        }
        val iosX64Test by getting
        val iosArm64Test by getting
        val iosSimulatorArm64Test by getting
        val iosTest by creating {
            dependsOn(commonTest)
            iosX64Test.dependsOn(this)
            iosArm64Test.dependsOn(this)
            iosSimulatorArm64Test.dependsOn(this)
        }
    }
}
b
Looks ok overall, except that you should remove cocoapods block from
data
module (you're not exporting it directly, remember)
h
I don’t know why it cannot recognize it 🤔
b
Found your missing link!
Namely
export(project(":anotherKMMModule"))
bit
So to get things rolling, you need to add
export(project(":data"))
to your
kotlin.cocoapods.framework {}
in shared module.
h
Wow it fixed, Thank you a lot 🙏 But why must I add
export(project(":anotherKMMModule"))
?
b
Apple stuff is not my area of expertice, but I guess without it kotlin native compiler treeshakes/obfuscates unused stuff from
data
module when it builds
shared
module. In other words,
data
is available to
shared
kotlin code only, but not objc/swift. Think of it as gradle's
implementation
dependency. Once you export it to cocoapods, it makes it transitive to objc layer like gradle's
api
does for android.
Hope that makes sense.
I guess that's what @ayodele meant in the first place 😄 Oh the joy of trying to explain KMP stuff without knowing IOS stuff! 🤦‍♂️
Hmm, there's also
transitiveExport = true
flag. I wonder if that would just export all kotlin dependencies implicitly as opposed to you having to declare each explicitly. Would you mind replacing
export
statement from earlier with
transitiveExport = true
and let me know if it still works on ios side?
h
@Big Chungus I replaced it but the issue appeared again
b
Fair enough, the flag must be for something else then
h
Yeah probably
a
@Hossein Amini I see you following the clean architecture with
data
domain
presentation
. You can avoid exporting the data and domain layers if you limit their access to the
shared
module. This will help you not ending up with bloated framework.
557 Views