feat(v3/mobile): IOS/Android manager API + event rename; first-class mobile docs#5602
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds experimental Wails v3 mobile documentation across five new or updated pages, refactors iOS and Android native feature APIs from function-based to method-based patterns, and unifies event routing through ChangesMobile Platform Documentation & API Refactoring
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested labels
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
Updates the docs landing-page morphing headline to include “Mobile”, extending the existing Desktop → Server cycle to Desktop → Server → Mobile.
Changes:
- Add
"Mobile"to thewordsarray used by the inline morphing script.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- New Mobile section in sidebar (promoted out of Building & Packaging) - Mobile overview page: how it works, prereqs table, feature matrix, build tag rules - 'Your First Mobile App' tutorial: iOS Simulator + Android Emulator paths, step-by-step from zero to running app, responsive CSS, platform detection, haptics, production builds, troubleshooting - iOS and Android guides moved to guides/mobile/; old paths redirect - Landing page card updated: 'Mobile coming soon...' → iOS + Android listed - Both platform guides link back to the first-mobile-app tutorial for newcomers
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/src/content/docs/guides/build/android.mdx`:
- Line 7: The redirect frontmatter fields in
docs/src/content/docs/guides/build/android.mdx (line 7) and
docs/src/content/docs/guides/build/ios.mdx (line 7) are not being processed
because there is no redirect handling mechanism configured. Implement redirect
handling by adding Astro's standard redirects configuration in astro.config.mjs
to map the old paths /guides/build/android and /guides/build/ios to their new
destinations /guides/mobile/android and /guides/mobile/ios respectively.
Alternatively, you can install a community plugin like astro-redirect-from to
process the custom redirect frontmatter fields. Choose one approach and
implement it to ensure the legacy URLs properly redirect to the mobile section
paths.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 95c93447-bae4-4a94-ae7c-8e3f2dbf6ac9
📒 Files selected for processing (8)
docs/astro.config.mjsdocs/src/content/docs/guides/build/android.mdxdocs/src/content/docs/guides/build/ios.mdxdocs/src/content/docs/guides/mobile/android.mdxdocs/src/content/docs/guides/mobile/first-mobile-app.mdxdocs/src/content/docs/guides/mobile/index.mdxdocs/src/content/docs/guides/mobile/ios.mdxdocs/src/content/docs/index.mdx
✅ Files skipped from review due to trivial changes (2)
- docs/src/content/docs/index.mdx
- docs/src/content/docs/guides/mobile/android.mdx
…name events Convert the iOS and Android native-feature free functions into methods on platform manager singletons: application.IOSShare(...) -> application.IOS.Share(...) application.AndroidHaptic() -> application.Android.Haptic(...) application.IOSSetScrollEnabled(...) -> application.IOS.SetScrollEnabled(...) `application.IOS` / `application.Android` are zero-size singletons that only exist on their respective builds; all callers live in //go:build ios/android files. Rename the mobile bridge events away from the `native:*` prefix: - cross-platform events -> `common:*` (common:haptic, common:location, …) - iOS-only -> `ios:*` (ios:beginBackgroundTask, ios:backgroundTask) - Android-only -> `android:*` (android:foregroundService) The frontend keeps a single listener per event under `common:*`, matching the shared-codebase model. Updated the Objective-C/C bridge, the Android Java bridge (template + committed example build copy), the kitchen-sink example (Go handlers + frontend), IOS.md/ANDROID.md, the docs-site mobile guides and the unreleased changelog. Refs #5602
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@v3/examples/mobile/native_features_ios.go`:
- Around line 50-52: The issue is that `application.IOS.GetOrientation()`
returns a plain string value (like "portrait", "landscape", or "unknown"), not
JSON, so passing it to `jsonToMap()` will fail to parse and return an empty or
nil map. In the event handler for "common:getOrientation", replace the
`jsonToMap(application.IOS.GetOrientation())` call with a direct map
construction that wraps the orientation string as a map value, ensuring the
emitted event contains the proper map structure with the orientation data.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: cbc2893f-29f7-4750-9e03-b216faea9ba3
📒 Files selected for processing (23)
docs/src/content/docs/guides/mobile/first-mobile-app.mdxdocs/src/content/docs/guides/mobile/index.mdxv3/ANDROID.mdv3/IOS.mdv3/UNRELEASED_CHANGELOG.mdv3/examples/ios/ios_runtime_events_ios.gov3/examples/mobile/README.mdv3/examples/mobile/build/android/app/src/main/java/com/wails/app/MainActivity.javav3/examples/mobile/build/android/app/src/main/java/com/wails/app/WailsBridge.javav3/examples/mobile/frontend/main.jsv3/examples/mobile/ios_runtime_events_ios.gov3/examples/mobile/native_features_android.gov3/examples/mobile/native_features_common.gov3/examples/mobile/native_features_ios.gov3/examples/mobile/native_features_stub.gov3/internal/commands/build_assets/android/app/src/main/java/com/wails/app/MainActivity.javav3/internal/commands/build_assets/android/app/src/main/java/com/wails/app/WailsBridge.javav3/pkg/application/ios_runtime_api.gov3/pkg/application/mobile_features_android.gov3/pkg/application/mobile_features_ios.gov3/pkg/application/mobile_features_ios.hv3/pkg/application/mobile_features_ios.mv3/pkg/application/webview_window_ios.m
✅ Files skipped from review due to trivial changes (7)
- v3/examples/mobile/native_features_stub.go
- v3/examples/mobile/native_features_common.go
- v3/pkg/application/webview_window_ios.m
- v3/examples/mobile/README.md
- v3/UNRELEASED_CHANGELOG.md
- v3/pkg/application/mobile_features_ios.h
- docs/src/content/docs/guides/mobile/index.mdx
🚧 Files skipped from review as they are similar to previous changes (1)
- docs/src/content/docs/guides/mobile/first-mobile-app.mdx
…dia type mfPresentCamera set picker.mediaTypes to public.movie/public.image without checking the camera actually offers that type. On the iOS Simulator (and any device whose camera reports available but does not support video) the virtual camera exposes stills only, so requesting video made -[UIImagePickerController setMediaTypes:] throw an uncaught NSException -> SIGABRT. Check availableMediaTypesForSourceType: before configuring the picker and emit a graceful common:capture error instead, with an @try/@catch safety net. Pre- existing since #5562; surfaced by the Xcode 26 simulator's virtual camera. Refs #5602
… media list Follow-up to ec23d47: the previous fix pre-checked availableMediaTypesForSourceType:, but that list is unreliable on the iOS Simulator — it returns empty even when the camera is authorized and can present, so the pre-check would wrongly block capture. Verified on-simulator: the list stays empty across authorization states and a 3s poll, yet photo capture presents fine. Instead: photos use the picker's default media type (public.image, never throws); video opts into public.movie inside an @try/@catch so the 'No available types for source 1' NSException degrades to a graceful common:capture error instead of SIGABRT. Real devices list public.movie and record normally. Refs #5602
The pbxproj template left the c-archive PBXFileReference path and the build phase's 'go build -o' path unquoted, so a productName containing a space (the default config ships 'My Product') produced an invalid project.pbxproj — Xcode reported 'The project main is damaged ... parse error'. Quote both paths. Also give the mobile example a real, space-free productName (KitchenSink) and identifier. Refs #5602
…er control group - Add NSFaceIDUsageDescription to the iOS Info.plist(s). Tapping Authenticate called LAContext biometric evaluation, which hard-crashes on a real device without this key (same class as the camera/mic keys). The crash left the app white-screening on relaunch; a clean reinstall + this key resolve it. - Move result feedback out of one box at the bottom of each section into a compact inline output beside each control group (Share, Screen, Status bar, Security, Secure storage, Haptics, Location, Motion, Speech, Keyboard, Camera, Background), so results appear next to the control you tapped. Refs #5602
WKWebView's renderer (WebContent) process is reaped by iOS under memory pressure or after repeated background/foreground and launch cycles. With no webViewWebContentProcessDidTerminate: handler the view stayed permanently blank — the 'white screen on the 3rd+ launch' reported on device. Implement the delegate method to reload and respawn the renderer. Framework-level fix; affects all Wails v3 iOS apps. Refs #5602
…lash Two iOS fixes: - ios_create_webview_with_id manually called -loadView and -viewDidLoad, so UIKit ALSO loaded the view automatically — two WebViews/viewDidLoad passes per cold launch. Content loaded into one while the blank one was displayed → intermittent white screen after force-quit + relaunch. Load the view once via -loadViewIfNeeded. Verified on-device: one viewDidLoad per launch, renders reliably across repeated force-quit relaunches. - Also fix the missing withError: parameter on didFailProvisionalNavigation (the old selector never matched, so load failures were swallowed) and add a didFailNavigation:withError: handler. Flash: any non-zero IOSOptions.BackgroundColour is now applied to the app window automatically (no need to also set AppBackgroundColourSet), so the window matches the web background instead of flashing white before the WebView paints. The mobile example sets it to its web background (#1b2636). Refs #5602
The flag only existed to distinguish a deliberate fully-transparent app window
({0,0,0,0}) from the unset zero value — but a transparent app-window background
is meaningless (the window needs an opaque backing), so the distinction had no
real use. Drop it and treat any non-zero BackgroundColour as set; the zero
value means unset (defaults to white). Simpler API, one field instead of two.
Refs #5602
The app declared UILaunchStoryboardName but the build never bundled a compiled
storyboard, so iOS showed a white default launch screen; and the window was
created white in the delegate before Go could apply a background colour. Both
caused a white flash on (cold) launch.
- Add a LaunchBackground colour asset (#1b2636) and switch Info.plist(s) to
UILaunchScreen { UIColorName = LaunchBackground } — bundled via the asset
catalog the build already compiles.
- Colour the initial UIWindow/root view from the same LaunchBackground asset in
didFinishLaunching (timing-independent; falls back to white if absent).
Refs #5602
iOS suppresses a local notification's banner while the app that posted it is
in the foreground unless the app registers a UNUserNotificationCenterDelegate
and opts into presentation. Wails registered no such delegate, so notifications
fired but were delivered silently to Notification Center with no banner.
Add an MFNotificationDelegate (UNUserNotificationCenterDelegate) whose
willPresentNotification returns banner+list+sound (alert+sound pre-iOS 14) and
didReceiveNotificationResponse handles taps. Register it via
ios_notifications_init() — both at launch (see delegate change) and defensively
at the top of ios_post_notification so it is set regardless of build path.
The mobile example surfaces the new common:notification {presented}/{tapped}
signals so foreground delivery is visible in the UI.
Start the Go runtime from the app delegate's didFinishLaunchingWithOptions (after UIKit has launched) instead of concurrently with UIApplicationMain, which intermittently corrupted the FrontBoard launch handshake on device (blank cold launch / 0x8BADF00D). main.m is now a minimal bootstrap; because it no longer references a Go symbol the archive must be linked with -Wl,-force_load (set in all iOS Taskfiles) or the WailsAppDelegate class is dead-stripped. Also: register the notification delegate before launch finishes; remove the dead, archive-less build.sh.tmpl (+ rendered copies); align the older examples/ios bootstrap with the verified examples/mobile pattern; tidy the example go.mod.
Three bugs prevented a plain 'wails3 init' app from building/running on iOS, while the in-repo examples masked them: - overlay:gen read its main_ios.go template from the wails SOURCE TREE via repoRoot(); outside the wails repo that path doesn't exist, so it failed with 'file does not exist'. Read it from the embedded build_assets FS instead (same approach as xcode:gen). - run/deploy-simulator/deploy-device hardcoded a com.wails.<app>[.dev] bundle id for simctl/devicectl, but the real id comes from build/config.yml (e.g. com.example.<app>). simctl install succeeded but launch failed (code 4). Read CFBundleIdentifier from the built bundle's Info.plist instead. - the iOS build depended on common:generate:icons (icns/ico — not used by iOS, and its dir:build broke through the nested include) and a redundant generate:ios:bindings that raced build:frontend's own bindings -clean. Both removed; iOS icons come from Assets.xcassets via actool and bindings from build:frontend. Verified end-to-end: 'wails3 init' + 'wails3 task ios:run' builds, installs and launches a default app on the iOS Simulator.
…verified simulator screenshot
- ios.mdx: replace the inaccurate runtime blurb (which mixed Go and JS APIs and
referenced a non-existent IOS.Scroll.SetEnabled JS call) with proper sections:
* 'Native device features — the IOS manager': the Go application.IOS.* pattern
(one-shot actions, JSON query helpers) with correct Haptic values.
* 'Asynchronous results: common:* and ios:* events': the event model with a
full table mapping each event to the manager call that triggers it and its
payload (common:* = shared with Android, ios:* = iOS-only).
* 'Runtime WebView controls': the Go SetScrollEnabled/... toggles plus the
real frontend runtime (IOS.Haptics.Impact, IOS.Device.Info).
- first-mobile-app.mdx: add a screenshot of a default 'wails3 init' app running
on the iOS Simulator, captured by actually running the tutorial end to end.
The tutorial's command sequence (wails3 init -> wails3 task ios:run -> ios:logs:dev
-> ios:xcode) was verified by running it against a fresh project on the Simulator.
On a phone the starter app's 'Wails + <Framework>' heading (font-size: 3.2em) overflowed and wrapped onto two lines, and content ran to the screen edges under the notch. Applied the same responsive treatment to every template's style.css and index.html: - h1 uses clamp(1.9rem, 8vw, 3.2rem) so it fits one line on phones and stays 3.2rem on desktop. - .container gets box-sizing, a 720px max-width and safe-area-aware padding (env(safe-area-inset-*)) so content clears the notch/home indicator. - logos cap at 40vw and shrink on <=480px viewports. - viewport meta gains viewport-fit=cover so the safe-area insets apply on iOS. Verified on the iOS Simulator with a fresh 'wails3 init' app: the heading now renders on a single line. The docs tutorial screenshot is updated to match.
- The mobile guides stated 'Go 1.24+', but the generated project's go.mod (and wails/v3) require go 1.25.0. Corrected to 1.25+ in ios, android, first-mobile-app and the index requirements table. - first-mobile-app: replaced the six-bullet technical breakdown of 'wails3 task ios:run' (Xcode scaffold, c-archive, GOOS=ios, ad-hoc signing, ...) with a single beginner-friendly sentence plus a short 'first run is slow' tip. Verified against the code: config.yml keys (bundleID/displayName/version/ minIOSVersion), IOSOptions fields, and the what-works table (save-file dialog error, UIPasteboard clipboard, UIDocumentPicker) are all accurate.
A plain 'wails3 init' app could not run on Android. Four bugs, the same class
as the iOS ones (masked by the in-repo example):
- ensure-emulator had a go-task template with backslash-escaped quotes
({{if eq .HOST_ARCH \"arm64\"}}) that fails to parse ('unexpected \ in
operand'), aborting any android task. Replaced with a precomputed ANDROID_ABI
var.
- gradlew lost its executable bit when extracted from the embedded build assets
('permission denied', exit 127). chmod +x before invoking it.
- the app launched but showed ERR_CONNECTION_REFUSED: the c-shared library has
no main registered because main_android.go (which calls
application.RegisterAndroidMain in init) was only extracted to build/android/,
not the main package. Added 'wails3 android overlay:gen' (mirrors iOS
overlay:gen) to inject main_android.gen.go into the main package via
-overlay, wired into the c-shared build.
- dropped the bogus common:generate:icons (unused on android, breaks through the
nested include) and redundant generate:android:bindings (races
build:frontend's own bindings) deps.
Verified end to end: a fresh 'wails3 init' app builds, installs and renders on
the Android emulator.
…approach - ensure-emulator: start the emulator via 'nohup sh -c "... &"' so it is reparented to launchd and survives go-task reaping background jobs (a bare 'emulator &' is killed before the install/launch steps run); also tolerate the logcat grep exiting non-zero. - example: bring build/android/Taskfile.yml in line with the template (overlay wiring, gradlew chmod, dropped bogus deps) and remove the hand-written root main_android.go — the build overlay now injects main registration, so the example matches what a fresh project generates. Update main.go's comment to describe the ios/android overlay mechanism. Verified on the Android emulator: both a fresh 'wails3 init' app and the kitchen-sink example build, install and render (dark mode).
The iOS/Android build overlays (overlay.json + gen/) contain per-machine absolute paths and are regenerated each build, so they must not be committed. The example already ignored the iOS ones; add the Android equivalents, and add both to the fresh-project template .gitignore so 'wails3 init' projects ignore them too.
…kfile (Completes the previous commit, whose git add aborted on an already-removed path and silently dropped these files.) - ensure-emulator: daemonize the emulator via 'nohup sh -c "... &"' so it survives go-task reaping background jobs; tolerate the logcat grep exiting non-zero. - example: sync build/android/Taskfile.yml with the template (overlay wiring, gradlew chmod, dropped bogus deps) and update main.go's comment to describe the ios/android overlay mechanism (root main_android.go was already removed).
Summary
This PR now carries two related pieces of work:
1. Mobile API refactor
Manager pattern
The iOS/Android native-feature free functions are now methods on platform manager singletons:
application.IOSandapplication.Androidare zero-size singletons that only exist on their respective builds; all callers live in//go:build ios///go:build androidfiles. (38 iOS methods, 31 Android methods.)Event renaming —
native:*is goneMobile bridge events are now namespaced by reach:
native:haptic,native:location,native:share, …common:*native:beginBackgroundTask,native:backgroundTaskios:*native:foregroundServiceandroid:*The frontend keeps a single listener per event under
common:*, matching the shared-codebase model. Applied consistently across every emit and listen site: the Objective-C/C bridge (.m/.h), the Android Java bridge (build-asset template and the git-tracked examplebuild/copy), the kitchen-sink example (Go handlers +frontend/main.js),IOS.md/ANDROID.md, and the docs-site guides.Files touched
v3/pkg/application/{mobile_features_ios.go,mobile_features_android.go,ios_runtime_api.go,mobile_features_ios.m,mobile_features_ios.h,webview_window_ios.m}, the Android Java bridge (template + example build copy),v3/examples/{mobile,ios}/…,v3/IOS.md,v3/ANDROID.md,v3/UNRELEASED_CHANGELOG.md, and the docs-site mobile guides.✅ Verification status — built & launched on both platforms
go build ./...andgo test ./pkg/application/...pass; the example builds clean for desktop.wails3 task ios:runbuilds the iOS-tagged Go C-archive (application.IOS.*managers), compiles + links the Objective-C bridge (renamed event strings), installs and launches. App runs and renders correctly withPlatform: iOS.wails3 task android:runbuildslibwails.so(cgoapplication.Android.*managers via the NDK),compileDebugJavaWithJavaccompiles the Java bridge (renamed event strings), assembles + installs the APK, launchesMainActivity. App runs with a working Go↔JS binding (Platform: ANDROID).native:suffix dropped/merged); alljsonToMap()calls now take JSON-returning getters (fixed the iOS orientation handler).androidBridgeVoid/androidBridgeStringStringmissing underCGO_ENABLED=0), surfaced only ingenerate bindingswarnings — present onmaster, unrelated to this change, and harmless to the real cgo build.2. Docs & landing page
'Mobile'to theMorphTextcycling words — H1 now cycles Desktop → Server → MobileMobile docs section
/guides/mobile) — how it works, prereqs, feature matrix, build-tag rules, featured Kitchen Sink callout with run commands/guides/mobile/first-mobile-app) — step-by-step zero-to-running tutorial (iOS Simulator + Android Emulator), responsive CSS, platform detection in Go and JS, the platform-gating pattern, haptics, production builds, troubleshooting. Includes a note documenting the newapplication.IOS.*/application.Android.*manager API and thecommon:*/ios:*/android:*event convention./guides/mobile/ios) and Android (/guides/mobile/android) — full reference guides, moved from/guides/build/*(links updated across the docs; no redirect stubs)Screenshots
Test plan
v3/examples/mobileon iOS (Xcode 26.5) and Android (NDK 26.3) — both compile, install, launch and render/guides/mobileand/guides/mobile/first-mobile-apprender correctlySummary by CodeRabbit