Skip to content

KMP migration M2: iOS support, device verification round, CMP rebrand - #14

Merged
michelutke merged 36 commits into
feature/kmp-migration-m1from
feature/kmp-migration-m2
Jul 23, 2026
Merged

KMP migration M2: iOS support, device verification round, CMP rebrand#14
michelutke merged 36 commits into
feature/kmp-migration-m1from
feature/kmp-migration-m2

Conversation

@michelutke

@michelutke michelutke commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

What

M2 on top of #15: iOS support, first real device-verification round (which found and fixed several release blockers), and the repo rebrand to the multiplatform SDK.

  • iOS targets + actuals on :updraft-core (iosArm64, iosSimulatorArm64, iosX64): screenshot grabber (UIGraphicsImageRenderer), foreground observer, NSUserDefaults store, app info, and shake — see "How" for why shake needed three mechanisms.
  • :updraft-ui-compose on iOS: same CMP feedback UI; UpdraftIos.autoWire() presents dialogs/feedback on the topmost view controller; UpdraftFeedbackViewController for manual hosting.
  • Feedback UI restyled to the legacy design (charcoal chrome, logo header, color swatches + selection halo, dashed undo, draw-hint overlay, white form fields, yellow buttons) — specs and PNG assets recovered from the deleted 1.x resources in git history; safeDrawing insets.
  • New feedback payload fields: device_uudid and navigation_stack, with Updraft.navigationStackProvider for single-activity apps (Compose Navigation etc.) and UpdraftSettings(sendNavigationStack = false) opt-out. README documents per-library integration.
  • Sample restructured to CMP (sample/composeApp + sample/iosApp), keys loaded from local.properties instead of hardcoded.
  • XCFramework export for pure-Swift consumers; CI ios job (macos-15: simulator tests, XCFramework, sample xcodebuild).
  • Relative release age in the update dialog: create_at renders as "Released 3 weeks ago. You're currently on version X." (legacy parity, kotlin.time, unit-tested buckets; plurals live in plurals.xml outside Loco's reach). Strings are synced with Loco again — asset IDs renamed to underscores, missing assets added, updateLoco round-trip verified.
  • Rebrand: repo becomes updraft-sdk (root project name, POM URLs, README repositioning; supersedes updraft-sdk-ios). Overview doc: docs/cmp-migration-overview.md.

Why

  • iOS was the point of the KMP migration — this makes the shared core + shared Compose UI actually run there.
  • The verification round existed because M1 was only unit-tested: first launches on real hardware surfaced bugs that compile fine and only explode at runtime.
  • Rebrand: decided to keep this repository as the single home for both platforms instead of a fresh repo.

How / notable findings (each cost real debugging)

  1. :updraft-sdk crashed on any dialog — the module never applied the Compose compiler plugin, so setContent lambdas compiled as plain Function0NoSuchMethodError at runtime. No compile error (the diagnostic comes from the plugin itself).
  2. Server field is device_uudid (double-U, a 2018 typo in the backend/iOS SDK contract) — sending the sane device_uuid silently drops the value.
  3. iOS shake is a responder-chain event (motionEnded:), not accelerometer data. The SDK hooks UIWindow.motionEnded:withEvent: via the ObjC runtime so no host-app integration is needed (same approach as Instabug); the IMP is a staticCFunction because Kotlin block bridging marshals the NSInteger motion argument as an object and segfaults. CoreMotion (device) and the legacy screenshot-notification trigger are kept as well.
  4. BASE_URL_STAGING (u2.mqd.me) is dead — removed from the public API; baseUrl stays overridable.
  5. Navigation stack capture happens at trigger time (before Updraft UI covers the app): activity stack on Android, view-controller chain walk on iOS, Updraft's own screens excluded.

Verified

  • CI green (android + ios jobs).
  • Android, Pixel 8 Pro against production: hint dialog → shake → annotate → send → received on dashboard incl. UUID + navigation stack; update dialog E2E (uploaded versionCode-6 release APK, v5 installed → dialog → install page).
  • iOS, iPhone 16 simulator: hint dialog, shake (⌃⌘Z) → feedback UI, form flow.
  • Update feature on iOS verified by construction (request/response models field-identical to the legacy iOS SDK, same endpoints proven via Android E2E); signed-IPA run needs an Apple team → release checklist.
  • Remaining release items in docs/kmp-migration-m1-status.md.

Closes #13 — with iOS targets and the Compose Multiplatform UI shipping here, the SDK is usable from KMP/CMP projects on both platforms.

… to updraft-sdk

Without the Compose compiler plugin, updraft-sdk's setContent lambdas
compiled as plain Function0 and crashed with NoSuchMethodError when any
Updraft dialog opened. The sample also lacked the :updraft-sdk dependency,
so no collector was subscribed to Updraft.events on Android and all
dialogs were silently dropped.
Charcoal chrome with Updraft logo header, color swatches with selection
halo, dashed undo button, draw-hint overlay, white box form fields and
yellow action buttons, per legacy layouts from dd72439^. Content now
padded by safeDrawing insets. Default stroke black at 4dp like legacy
FreeDrawView.
Staging base URL u2.mqd.me is unreachable (connection fails), which made
every feedback upload and update check fail. Sample now uses the default
production base URL where the configured keys are valid.
…ng URL

The server field is device_uudid (as sent by the iOS SDK), not
device_uuid, so the UUID never showed up on the dashboard. Navigation
stack was never implemented on Android; it now reports the host app's
activity stack (Android) or view controller chain (iOS), excluding
Updraft's own screens. BASE_URL_STAGING pointed at u2.mqd.me which no
longer resolves; baseUrl stays overridable for self-hosted instances.
Navigation stack is captured when feedback is triggered, before the
Updraft UI covers the app. Apps using single-activity navigation
(Compose Navigation, Navigation Component) can plug their library in
via Updraft.navigationStackProvider; the platform default reports the
activity stack (Android) or view controller chain (iOS). Also fixes
the package filter that swallowed host activities whose package merely
shares the com.appswithlove.updraft prefix, like the sample app.
The legacy iOS SDK never used shake detection; its feedback trigger was
UIApplicationUserDidTakeScreenshotNotification. The accelerometer-based
shake also cannot work on the simulator (no accelerometer), so shake
appeared broken there. Both triggers are now active on iOS: screenshot
(all environments, legacy parity) and shake (physical devices).
Shake reaches iOS apps as a responder-chain motion event, not as
accelerometer data, so the CMMotionManager detector never fired on the
simulator and required nothing in the responder chain to consume the
event on devices. The SDK now swizzles UIWindow.motionEnded:withEvent:
at start, calling through to the original implementation. The IMP is a
staticCFunction rather than a Kotlin lambda: block bridging would
marshal the NSInteger motion argument as an object and crash.
The repo stays and becomes the single home for Android + iOS: root
project renamed to updraft-sdk, POM name/URLs point at the new repo
slug, README repositions the SDK as Kotlin/Compose Multiplatform and
notes that updraft-sdk-ios is superseded. GitHub-side rename and
archiving updraft-sdk-ios remain manual release-time steps.
@michelutke michelutke self-assigned this Jul 22, 2026
@michelutke michelutke changed the title Feature/kmp migration m2 KMP migration M2: iOS support, device verification round, CMP rebrand Jul 22, 2026
… filter

Superseded runs on the same ref are cancelled, ~/.konan is cached (the
Kotlin/Native toolchain was re-provisioned on every macOS run before),
gradle/actions/setup-gradle replaces the basic setup-java cache and
validates the wrapper jar, markdown/docs pushes no longer trigger
builds, timeouts bound hung runs, and the redundant pull_request
trigger and chmod steps are removed (exec bit is committed).
…rsions

Publishing previously ran no tests: a bad push to production would ship
broken artifacts that Maven Central cannot replace. The job now runs
the full build plus iOS simulator tests first, fails fast if the
version is already tagged, publishes under a 'maven-central' GitHub
environment (protection rules can be added in settings), and uses the
maintained softprops/action-gh-release instead of the archived
actions/create-release. JDK unified to 17 to match CI.
The SDK is built with Kotlin 2.2 / CMP 1.9: Kotlin Android consumers
need >= 2.1 (metadata n+1 rule), KMP/CMP consumers >= 2.2, Java-only
and XCFramework consumers are unaffected. The README lists the exact
compile-time errors older projects will see and the fix.
The legacy SDK fetched strings from Loco into Android res/; Compose
Multiplatform resources use the same strings.xml format, so updateLoco
now writes into updraft-ui-compose commonMain composeResources and both
platforms share the result. The API key moves from the build file
(exposed in git history — rotate it) to local.properties.
@michelutke
michelutke requested a review from yannickpulver July 22, 2026 14:00
@michelutke
michelutke requested review from vladislavfrolov and removed request for yannickpulver July 23, 2026 06:53
Loco is the source of truth again: asset IDs renamed to underscores
(compose-resources rejects dots), the ten strings missing from Loco
added with German translations, and the draw-hint copy fixed there.
This file state is the updateLoco output: alphabetical order and the
removal of two unused legacy plurals (relative-age formatting was never
implemented in 2.0).
Restores the legacy 'Released 3 weeks ago. You're currently on version
X.' message using kotlin.time (no new dependency). Buckets: just now
under an hour, then hours/days/weeks/months/years plurals. The plurals
live in plurals.xml so updateLoco, which only rewrites strings.xml,
cannot clobber them. Falls back to the previous message when create_at
is missing or unparseable.
Comment thread updraft-ui-compose/build.gradle.kts
@michelutke
michelutke merged commit 0b3316e into feature/kmp-migration-m1 Jul 23, 2026
@michelutke
michelutke deleted the feature/kmp-migration-m2 branch July 23, 2026 13:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants