Appearance
Troubleshooting Guide
This section documents real issues encountered while integrating the SDK, in the order a developer is likely to encounter them, along with their root cause and solution. Each entry corresponds to an issue that was actually diagnosed during SDK development.
1. Kotlin Long ↔ Swift Type Mismatch
Symptom
Compile error or runtime cast exception around amount fields, for example:
swift
Int32(item.harga) as! KotlinLong // Invalid — these are unrelated typesCause
Kotlin Long is exposed to Swift as the boxed class KotlinLong, while Kotlin Int is exposed as native Int32.
These types cannot be cast into each other.
Fix
Wrap Long fields explicitly, while leaving Int fields unchanged.
swift
// Long fields (e.g. PaymentDetails.amount)
amount: KotlinLong(value: Int64(item.harga))
// Int fields (e.g. ItemDetails.amount)
amount: Int32(item.harga)2. "View is not in the window hierarchy" when presenting on iOS
Symptom
text
Attempt to present <ComposeHostingViewController> on
<PresentationHostingController...> whose view is not in the window hierarchy.Cause
The SDK's UIViewController is presented in the same execution flow that dismisses a SwiftUI .sheet.
The presenting controller has not yet returned to the window hierarchy.
Fix
Dismiss the sheet first, then present the SDK after observing the state change using .onChange, instead of relying on a fixed delay.
swift
@State private var selectedItem: Item? = nil
@State private var pendingItem: Item? = nil
.sheet(item: $selectedItem) { item in
ConfirmationSheet(onConfirm: {
pendingItem = item
selectedItem = nil
})
}
.onChange(of: selectedItem) { newValue in
if newValue == nil,
let item = pendingItem {
pendingItem = nil
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
presentPaymentGateway(item: item)
}
}
}More Robust Variant
Resolve the presenter from the application's key window and retry while the current presenter is still being dismissed.
swift
func resolvePresenter(
attempt: Int = 0,
_ completion: @escaping (UIViewController) -> Void
) {
guard let root = UIApplication.shared.connectedScenes
.compactMap({ $0 as? UIWindowScene }).first?
.windows.first(where: { $0.isKeyWindow })?
.rootViewController else {
return
}
var top = root
while let presented = top.presentedViewController {
top = presented
}
if top.isBeingDismissed || top.view.window == nil,
attempt < 10 {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
resolvePresenter(attempt: attempt + 1, completion)
}
return
}
completion(top)
}3. IrLinkageError: HttpTimeout.Plugin
Symptom
Application crashes during the first Compose render on iOS.
text
kotlin.internal.IrLinkageError:
Can not get instance of singleton 'Plugin':
No class found for symbol
io.ktor.client.plugins/HttpTimeout.Plugin|null[0]Cause
Different Ktor versions are declared across source sets.
Example:
commonMain→ktor-client-core:3.1.3iosMain→ktor-client-darwin:2.3.7
Ktor 2.x and 3.x are incompatible internally.
Fix
Align every Ktor dependency to the exact same version.
kotlin
val ktorVersion = "3.1.3"
implementation("io.ktor:ktor-client-darwin:$ktorVersion")Also remove duplicate Android dependencies pinned to an older version.
After Updating Versions
Run a clean build.
bash
./gradlew clean
./gradlew assemblePaymentGatewaySDKReleaseXCFramework4. Exception Appears as NSTaggedDate
Symptom
LLDB displays:
text
Exception (__NSTaggedDate *)
2001-01-01 00:00:00 UTCCause
This is not the real exception.
It occurs when a Kotlin Throwable crosses the Objective-C bridge without being caught correctly.
Common causes include:
- Callback signature mismatch
- Debugger stopping at the wrong stack frame
Fix
Create an Objective-C exception breakpoint.
bash
breakpoint set -E objcRestart the application completely.
bash
po $arg1The real exception will now be visible.
Alternative
Wrap risky initialization inside try/catch.
kotlin
val viewModel = remember {
try {
PaymentViewModel(
token = token,
jsonData = json,
onResultCallback = onResult
)
} catch (e: Throwable) {
Napier.e(
tag = "INIT",
message = "VIEWMODEL CONSTRUCT FAILED: ${e::class.simpleName} - ${e.message}"
)
throw e
}
}5. Callback Closure Signature Mismatch
Symptom
Old Swift code still assumes:
swift
(Boolean) -> UnitExample:
swift
) { success in
if success as? Bool == true {
print("Success")
} else {
print("Failed")
}
}Fix
Use the new PaymentSdkResult object.
swift
) { result in
if result.isSuccess {
print("Success, link: \(result.link ?? "-")")
} else {
print("Failed: \(result.responseMessage ?? "-")")
}
}Prevention
Whenever a public callback signature changes:
- Rebuild the
.xcframework - Perform a full clean before testing
6. LLDB / Xcode Messages That Can Be Ignored
The following debugger messages are expected and do not indicate SDK issues.
| Message | Why It's Harmless |
|---|---|
could not execute support code to read Objective-C class data | LLDB limitation when inspecting optimized binaries |
Can't show file for stack frame ... Trace.uikit.kt | Debug symbols reference the CI build server path |
Unable to simultaneously satisfy constraints ... TUIKeyplane.right | Internal iOS keyboard layout warning |
RTIInputSystemClient ... requires a valid sessionID | Internal iOS text-input session warning |
Annex A PaymentViewModel Public Surface
Reference summary of the public functions exposed by PaymentViewModel.
| Function | Purpose |
|---|---|
updateCardNumber(number) | Updates card number, formats spacing, detects card type |
updateHolder(name) | Updates cardholder name and converts it to uppercase |
onExpiryDateChange(value) | Formats expiry date as MM/YY |
onCvvChange(value) / updateCvv(value) | Updates CVV; updateCvv() also flips the card preview |
flipCard(showBack) | Flips the card preview manually |
openNfc(context) / closeNfc(context) | Starts/stops NFC session |
autofillFromNfc(number, expiry, name) | Populates card information from NFC |
validateAll() | Validates every field |
onPayClicked(scope) | Performs validation then triggers payment |
processPayment(scope) | Builds PaymentRequest and calls PaymentGateway.charge() |
onSuccessDone() / onFailedDone() | Invokes host callback with PaymentSdkResult |
reset() | Resets CardState |
Annex B PaymentSdkResult Reference
kotlin
data class PaymentSdkResult(
val isSuccess: Boolean,
val responseCode: String? = null,
val responseMessage: String? = null,
val link: String? = null,
)
fun PaymentResponse.toSdkResult(
isSuccess: Boolean
): PaymentSdkResult =
PaymentSdkResult(
isSuccess = isSuccess,
responseCode = responseCode,
responseMessage = responseMessage,
link = data?.link,
)Both onSuccessDone() and onFailedDone() create this DTO before invoking the host application's callback.
Annex C Reference Implementation: iOS Host App
Contains the complete SwiftUI reference implementation demonstrating:
MainViewController()exported from Kotlin Multiplatform- Safe presentation after dismissing SwiftUI
.sheet - Building
PaymentRequest - Creating Base64 credentials
- Launching the SDK
- Receiving
PaymentSdkResult
The full source code is included in the original documentation.
Annex D Gradle Configuration Reference
Key Gradle configuration illustrating proper Ktor version alignment.
kotlin
val ktorVersion = "3.1.3"
implementation("io.ktor:ktor-client-core:$ktorVersion")
implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
implementation("io.ktor:ktor-client-auth:$ktorVersion")
implementation("io.ktor:ktor-client-okhttp:$ktorVersion")
implementation("io.ktor:ktor-client-darwin:$ktorVersion")Important: Every Ktor dependency across all source sets must use the exact same version to avoid
IrLinkageError.