Abhishek Agarwal
08/18/2024, 6:24 PMDaniel Seither
08/19/2024, 6:04 AM0.23.1
• If it’s run for any other branch, it increments the version from the latest one on the main branch and adds a pre-release tag based on the branch name (0.23.2-cool-new-feature.0
for the first build of that branch, 0.23.2-cool-new-feature.1
for the next one).Abhishek Agarwal
08/19/2024, 11:59 AM0.23.2-cool-new-feature.1
does this create any problem or how you deal with it ?Daniel Seither
08/19/2024, 12:26 PMAdditional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format.(semver.org) We host the XCFramework on S3, but you could use any other storage that you can get SPM to pull from via HTTPS.
Daniel Seither
08/19/2024, 12:28 PMkpgalligan
08/19/2024, 4:16 PMkpgalligan
08/19/2024, 4:18 PMhow should we structure our development workflow when using KMMBridge to iteratively build and tag SPM packages for feature development?This is a different discussion. By "feature development" you mean architecture and direct app dev? This is a major focus on mine on scaling KMP. KMMBridge is a library publishing tool. Trying to do "feature dev" using a library model doesn't scale well (depending on team size) https://touchlab.co/kmp-teams-piloting-vs-scaling
kpgalligan
08/19/2024, 4:19 PMDaniel Seither
08/19/2024, 5:03 PMWe’ll need to update some guidance if SPM actually supports those kinds of labels.We’ve been doing this for almost two years now and never had any issues with it. Happy to answer any questions you have!
Abhishek Agarwal
08/19/2024, 6:17 PM0.2.3-some-feature.1
, and when merged to main, it should create a tag like 1.0.0
. How can I configure this in KMMBridge?kpgalligan
08/19/2024, 6:18 PMkpgalligan
08/19/2024, 6:26 PMHow can I configure this in KMMBridge?There's a new version of KMMBridge coming out soon. You don't have to use it. You can configure the current version if you want. I assume you're using the GitHub actions workflow with "autoversion". That's all going away. It adds too much complication. Now, it's important to understand that in the 0.5.x versions of KMMBridge, which I assume you're on, the autoversion stuff was all done in GitHub actions (in the 0.3.x KMMBridge versions, Gradle versioning was manipulated inside the plugin, which is a terrible idea). Anyway, you can just ignore the autoversion provided by GitHub actions by changing your build Gradle file. In the 0.5.x version template, the version is applied this way
val autoVersion = project.property(
if (project.hasProperty("AUTO_VERSION")) {
"AUTO_VERSION"
} else {
"LIBRARY_VERSION"
}
) as String
subprojects {
val GROUP: String by project
group = GROUP
version = autoVersion
}
Just ignore that and do something like this:
val buildVersion = project.property("BUILD_VERSION") as String
subprojects {
val GROUP: String by project
group = GROUP
version = buildVersion
}
Then just make sure you set BUILD_VERSION
to whatever you want in Gradle properties (either in source or passed to the build).
Essentially, KMMBridge is getting out of the "version numbering business".kpgalligan
08/19/2024, 6:29 PMWhen using the bidirectional approach, I noticed that the XCFramework is integrated directly into Xcode.That would be my default, but it's not required. You are building from source locally, though.
However, we use SPM to modularize our codebaseI'm writing a blog post about this now. You're thinking of KMP as a library. It's not. When you're writing feature code, it's part of your app code. If you modularize you Swift feature code in frameworks and use SPM to pull that in, OK, but that seems inefficient. Unless you mean you've configured SPM to build everything, including local Swift code, and Xcode isn't really managing "builds"
kpgalligan
08/19/2024, 6:30 PMsince SPM doesn’t have direct access to the main app target, it can’t access the API from the framework.I'm not sure what you mean here. Can you expand on this?
Abhishek Agarwal
08/19/2024, 6:51 PMval buildVersion = project.property("BUILD_VERSION") as String
subprojects {
val GROUP: String by project
group = GROUP
version = buildVersion
}
in my shared module’s Gradle file, or is this something that needs to be configured for the GitHub Actions? My primary goal is to generate the SPM package, so I plan to use the iosPublish action.Abhishek Agarwal
08/19/2024, 6:52 PMkpgalligan
08/19/2024, 7:06 PMIn our main app, we use separate SPM modules for each feature, encapsulating its data, domain, and presentation layersYou can essentially have one KMP XCFramework, so using multiple modules in this way with KMP probably wouldn't work, unless I'm not understanding this completely.
Given this setup, how would things work with the bidirectional approach?If you need to use SPM in that way, then you'll need to have the KMP/SPM module as a dependency of each of the feature modules that use the KMP (Feature A, Feature B, etc). If you're strictly talking the bidirectional approach, that implies you're building from source locally. SPM has horrible integrations outside of what's stock to SPM. For example, there's no way that I know of to trigger Gradle to build the KMP from SPM. We have a local SPM dev flow. Confusingly, it's part of KMMBridge currently, although we should probably separate that out. Anyway, you run Gradle on the command line to build your KMP binary, then run SPM with that as a dependency. So I understand better, the modules Feature A, Feature B, are they all Swift/Objc built by SPM? Does all of that live in an app repo, or are you pulling those features liked versioned libraries from somewhere?
Abhishek Agarwal
08/19/2024, 7:09 PMkpgalligan
08/19/2024, 7:14 PMDaniel Seither
08/20/2024, 6:31 AM.package(url: "<https://github.com/org/lib>", exact: "0.11.22-somebranch.0")
. When doing local KMP development, we can comment this out and replace it with a local package: .package(path: "../../lib")
(checkouts are side by side). The local Package.swift looks like this:
// swift-tools-version: 5.6
import PackageDescription
let package = Package(
name: "SomeLib",
products: [
.library(
name: "SomeLib",
targets: ["SomeLib"]
),
],
targets: [
.binaryTarget(
name: "SomeLib",
path: "build/XCFrameworks/debug/SomeLib.xcframework"
),
]
)
The downside of this setup: we need to build the XCFramework manually after each change because building in Xcode doesn’t trigger a Kotlin build. But to be honest, we like building the lib against unit tests anyhow and only then consume it in the apps, which reduces the amount of back and forth between both worlds. We also only share business logic, not view models etc., so we don’t need as much parallel development of lib and app.kpgalligan
08/20/2024, 12:35 PMIf we wouldn’t constrain the use of Kotlin types to a very small integration layer, we’d have much slower SwiftUI previewsWhy?
such as all the limitations that come with the intermediate Objective-C layerAny specifics on which limitations? Have you tried SKIE, or is that not what you're talking about? Not that SKIE solves everything, but I'm curious. Objc isn't so much the problem. Kotlin and Swift are very different.
but also Kotlin data classes being reference types while SwiftUI liking value types for state.Why not observable objects or similar?
Daniel Seither
08/21/2024, 3:22 PM@State
vars as much as possible, which worked out better for us and seems to be a general trend in the SwiftUI space. In sync with the general direction of the Swift community, we default to value types (structs/enums) for modeling state and try to steer clear of inheritance. Unfortunately, Kotlin and Objective-C don’t really support value types, so we translate between the worlds in the integration layer.
In summary, there are already so many considerations that are going into design decisions for our feature modules that we don’t want to add any more constraints by factoring KMP into the mix. We’re much happier with isolating KMP to the integration layer.kpgalligan
08/21/2024, 3:44 PMThe biggest annoyance that we’ve run into is the extremely limited support for genericsI'm familiar (I added them). Wait till Swift export arrives. It'll be worse (unless there's a magic solution). Objc generics are only on classes, but you can cast around variance issues. Swift generics do not work that way.
kpgalligan
08/21/2024, 3:45 PMeven though Swift would be totally happy with what we’re doing in KotlinYou'd be surprised.
kpgalligan
08/21/2024, 3:46 PMI’m hoping for the direct Kotlin to Swift export that JetBrains is working on, even though the first version that will ship with 2.1 won’t support generics at all, AFAIRSwift is very strict on variance. I wrote the ObjC implementation (the initial one, anyway, not sure how much it's been updated). I've been saying for years. Swift interop is necessary for KMP's long term, but Swift and Kotlin are very different languages. Generics will be brutal.
kpgalligan
08/21/2024, 3:53 PMAnyClass
rather than Any
for generics (I think, my names may be off). SKIE doesn't generate a full Swift wrapper interface. It's partial. The team wanted to add that, but I kind of shut that down because of effort and the time value, considering Swift Export is coming, although I suspect it'll a lot longer than we'd all hope for the features out of scope to be in scope. That's a long discussion in itself. Again, Swift and Kotlin look similar, but they're really, really different languages. Generics, Flows, default params, sealed classes, (lack of) structs, and so much more. Swift interop is not the same as "Swift fluency".kpgalligan
08/21/2024, 3:57 PMDaniel Seither
08/21/2024, 4:35 PMkpgalligan
08/21/2024, 4:40 PMit’s a good idea to isolate KMP from the feature code, to constrain any interop considerations to a much smaller part of the code baseThe trick there is KMP being relegated to small portions of the code, unless I'm not understanding what you're say. It reduces the value of KMP. Either less code in KMP, or significantly reduced dev efficiency, or both. For KMP to really be valuable while sharing code (not doing Compose UI, etc), KMP needs to be used for a significant portion of development, and it needs to do so efficiently. I'm obsessed with that. Otherwise it'll be another "also ran/tried that" cross-platform thing. https://touchlab.co/kmp-teams-piloting-vs-scaling
Daniel Seither
08/21/2024, 5:12 PMkpgalligan
08/21/2024, 5:16 PMWe’ve been sharing code (mostly business logic) between apps with a native UI with a library development model for the better part of a decade now ... and we learned that we overdid the code sharing when we tried letting the library drive the native UI in some parts of the app.Yeah, you can. We were heavily into J2ObjC before KMP, and started with KMP when it first was possible. It's just that, for most teams, they simply won't change or invest the way yours did. Also, if you really want to use KMP for direct UI architecture, not just business logic (depending on how you define "business logic"), the library model is terrible. Not for small teams so much, although it's not fun. If you had 10 android and 10 iOS devs, trying to do that would be worse than just writing everything native. That's the problem I'm trying to figure out.
Daniel Seither
08/21/2024, 5:19 PMAlso, if you really want to use KMP for direct UI architecture, not just business logic (depending on how you define “business logic”), the library model is terrible.💯
kpgalligan
08/21/2024, 5:21 PM