Install
$ agentstack add skill-peterhdd-agent-skills-engineering-mobile-app-builder Open-source listing, not yet scanned by AgentStack. Follow the source repository for install instructions.
Security review
⚠ Flagged1 finding(s); flagged for manual review. · v0.1.0 How review works →
- • Prompt-injection patterns
- • Secret / credential exfiltration
- • Dangerous shell & filesystem operations
- • Untrusted network calls
- • Known-malicious package signatures
- high Destructive filesystem operation.
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Mobile Development Guide
Overview
This guide covers native iOS/Android development (SwiftUI, Jetpack Compose) and cross-platform frameworks (React Native, Flutter) with patterns for offline-first architecture, platform integrations, and performance optimization. Use it when choosing a platform strategy, building mobile UI, or integrating device capabilities.
Platform Selection Guide
- Use native (SwiftUI/Compose) when the app requires deep platform integration (widgets, extensions, AR, custom camera pipelines).
- Use React Native or Flutter when shipping to both platforms with a small team and the app is primarily data display and forms.
- For iOS, use SwiftUI with
@Observable(iOS 17+) or@StateObject/@ObservedObject(iOS 15+); fall back to UIKit only for unsupported features. - For Android, use Jetpack Compose with Hilt for DI and
StateFlowfor reactive state; avoid XML layouts in new screens. - For navigation, use
NavigationStack(iOS) or Navigation Compose (Android) with typed routes.
Performance and UX Rules
- Cold start must be under 2 seconds on mid-range devices; defer initialization not needed for first frame. If cold start exceeds 1.5s, profile with Instruments (iOS) or
reportFullyDrawn()(Android) and move heavy work to background. - Maintain 60fps scrolling on devices two generations behind current. If frame drops occur: on iOS, check for off-main-thread image decoding and use
prefetchDataSource; on Android, enablecompositionStrategy = ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyedand check for unnecessary recomposition with Layout Inspector. - Animations: use SwiftUI
.animation()/ ComposeanimateFloatAsState; keep durations 200-350ms. Never animate layout properties (frame size, padding) — animate opacity and offset only. - Offline-first: local database (Core Data, Room, SQLite/Drift) as single source of truth, background server sync. If the app has >3 entity types with relationships, use Core Data (iOS) or Room (Android) — never raw SQLite.
- Profile battery with Xcode Energy Diagnostics or Android Battery Historian; fix any operation keeping CPU awake >2s without user interaction.
- Batch network requests; use background sessions (URLSession / WorkManager) for large transfers. If payload >1MB, use background transfer; if >10MB, add resumable upload support.
- Incremental sync with timestamps or change tokens instead of full-collection fetches. If collection has >500 items, paginate sync with cursor; never fetch all.
- Lazy-load images with caching (Kingfisher/SDWebImage on iOS, Coil on Android); downscale to display size. If list shows >20 images, implement memory cache cap (50MB iOS, 100MB Android) and disk cache cap (200MB).
- App binary size: target 3 permissions needed, request them progressively as features are used — never batch-request on launch.
Code Examples
See [SwiftUI Guide](references/swiftui.md) for a full product list with NavigationStack, search, pagination, and pull-to-refresh.
See [Jetpack Compose Guide](references/compose.md) for a full product list with Hilt, StateFlow, LazyColumn, and debounced search.
See [React Native Guide](references/react-native.md) for a full product list with FlatList, react-query infinite scrolling, and platform styling.
See [Flutter Guide](references/flutter.md) for a full product list with Riverpod, ListView.builder, debounced search, pagination, and pull-to-refresh.
See [Offline-First Architecture](references/offline-first.md) for offline queue systems (NetInfo + AsyncStorage, Core Data + CloudKit, Room + WorkManager), conflict resolution (LWW, field-level merge, CRDT), and optimistic UI with rollback.
See [Performance Patterns](references/performance.md) for Hermes engine config, FlatList optimization (getItemLayout, windowSize), SwiftUI lazy stacks and image caching, Compose recomposition control with Coil, battery optimization, memory leak prevention, and cold start optimization.
See [State Management](references/state-management.md) for Zustand with MMKV persistence, TanStack Query optimistic mutations, SwiftUI @Observable with environment injection, Compose ViewModel + StateFlow + SavedStateHandle, navigation deep linking, auth state machines, and form validation.
See [Native APIs](references/native-apis.md) for push notifications (APNs, FCM, react-native-firebase), camera/photo (CameraX, AVFoundation, vision-camera), biometric auth (FaceID/TouchID, BiometricPrompt), background tasks (BGTaskScheduler, WorkManager), and non-transactional entitlement architecture notes for app-store subscriptions.
Platform-Specific Gotchas
iOS Background Task Limits
BGAppRefreshTask: System grants ~30 seconds of execution. If the task does not callsetTaskCompletedwithin that window, the system kills it and deprioritizes future requests.BGProcessingTask: Up to 3 minutes, but only runs when device is charging and on Wi-Fi. Do not rely on this for time-sensitive sync.- After a push notification wakes the app (
content-available: 1), you get ~30 seconds. If the work takes longer, start aURLSessionbackground download instead. - The system learns user behavior. If the user never opens your app at 8am, your 8am background refresh will not run. Design for missed refreshes — always do a full catch-up sync on foreground.
Android Background Restrictions
- Doze mode (Android 6+): After screen off + stationary, network access is batched into maintenance windows (~every 15 min, increasing to ~1 hour).
WorkManagerwithNetworkType.CONNECTEDconstraint will defer until the next window. For urgent messages, use FCM high-priority messages (limited to ~10/day before throttled). - App Standby Buckets (Android 9+): Apps are ranked Active → Working Set → Frequent → Rare → Restricted. Rare/Restricted apps get severely limited jobs and alarms. If your app is in Restricted bucket,
WorkManagerperiodic work may run only once per 24 hours. - Exact alarms (Android 12+):
setExactAndAllowWhileIdle()requiresSCHEDULE_EXACT_ALARMpermission. Users can revoke it in Settings. Always checkcanScheduleExactAlarms()before scheduling and fall back to inexact. - Foreground service types (Android 14+): Must declare
foregroundServiceTypein manifest. Types:camera,location,mediaPlayback,dataSync, etc. Using wrong type → crash.dataSynctype is limited to 6 hours.
Flutter-Specific Gotchas
constconstructors: Always useconstfor stateless widgets and unchanging widget subtrees. Withoutconst, Flutter rebuilds the entire subtree on parent rebuild. This is the #1 Flutter performance issue.Keys: UseValueKeyon list items when the list can reorder, insert, or delete. Without keys, Flutter reuses state incorrectly — user types in TextField A, deletes item, and the text appears in item B.- Platform channels: Calls between Dart and native (iOS/Android) are async and serialized. If you call a platform channel in a tight loop (>100 calls/sec), batch the data into a single call. Use
BasicMessageChannelwith binary codec for large payloads. - Isolates: Dart is single-threaded. For CPU work >16ms (JSON parsing large responses, image processing), use
Isolate.run()(Dart 2.19+) orcompute(). Never parse >100KB JSON on the main isolate. - Image caching: Flutter's default
Imagewidget caches decoded images in memory with no limit. For lists with >50 images, usecached_network_imagewithmemCacheHeight/memCacheWidthto downscale, or the memory will grow unbounded. - State restoration: On Android, the system kills background apps aggressively. Use
RestorationMixinor persist critical state to disk. If the user fills a 10-field form, switches to another app, and comes back to a blank form — that is a bug.
Compose-Specific Gotchas
- Recomposition: Any lambda that captures a mutable value triggers recomposition of its parent. Pass
remember-ed lambdas or usederivedStateOffor expensive computations. Use Layout Inspector > "Show Recomposition Counts" to find hot spots. - Stability: Compose skips recomposition for
@Stableor@Immutabletypes. If your data class usesList(unstable), Compose recomposes every time. Usekotlinx.collections.immutable.ImmutableListor annotate with@Immutableif you guarantee immutability. LazyColumnitem keys: Always providekeyinitems(key = { it.id }). Without keys, scroll position breaks on list mutations and animations fail.
Offline Sync Decision Rules
Conflict Resolution Strategy
- Last-Write-Wins (LWW): Use when data is user-owned and rarely edited concurrently (user profile, settings, personal notes). Simple: compare timestamps, latest wins. Risk: silent data loss if two devices edit simultaneously.
- Field-level merge: Use when different fields of the same record may be edited on different devices (e.g., user edits
nameon phone,emailon tablet). Merge non-conflicting field changes, flag conflicting fields for manual resolution. Requires tracking per-field timestamps. - Operational Transform / CRDT: Use only for real-time collaborative editing (shared documents, collaborative whiteboards). Complex to implement — use a library (Yjs, Automerge). Never build custom OT/CRDT.
- Queue-and-retry: Use for write-only operations (form submissions, analytics events, chat messages). Queue locally, send when online, retry with idempotency keys. No conflict possible because each operation is independent.
Sync Architecture Decision Rules
- If the app has 1% at any stage, pause and investigate.
Anti-Patterns
- Never use
@ObservedObjectfor state owned by the current view — use@StateObject(iOS 15) or@Statewith@Observable(iOS 17+).@ObservedObjectis for injected dependencies only. - Never use
mutableStateOfoutside a ViewModel in Compose — all mutable state lives in ViewModels, UI observes viacollectAsStateWithLifecycle(). - Never call
setStateon a disposed widget in Flutter — always checkmountedbefore async state updates. In Riverpod, useref.onDispose()to cancel async work. - Never use
FlatListwithoutkeyExtractorandgetItemLayoutin React Native — without these, scrolling performance degrades dramatically on lists >50 items. - Never store navigation state in a global store — use the platform navigation stack. Global nav state causes back-button bugs and deep link failures.
- Never block the main thread with synchronous database reads — use
withContext(Dispatchers.IO)(Android) / background actors (iOS) /InteractionManager.runAfterInteractions(React Native) /Isolate.run()(Flutter). - Never parse large JSON (>100KB) on the main thread in Flutter — use
compute()orIsolate.run(). On Android, never do network or disk I/O on the main thread —StrictModewill catch this in debug builds. - Never use platform-specific code without a fallback. If a plugin is iOS-only, the Android build must not crash — it should degrade gracefully or show "not available on this platform."
Workflow
Step 1: Platform Strategy and Setup
- Choose native vs cross-platform: if app needs AR, custom camera, or widgets, go native. If app is data display + forms with 80% coverage.
- Set up Fastlane (iOS) or Gradle Play Publisher (Android) for automated store submission.
Self-Verification Protocol
After completing any mobile implementation, verify before submitting for review:
- Run the app on a real device (not just simulator). Simulators hide performance, memory, and gesture issues.
- Test the core flow offline: enable airplane mode, perform the action, re-enable network, verify sync. If the app crashes or loses data offline, it is not ready.
- Profile memory on a mid-range device. If peak memory exceeds 200MB, investigate before shipping.
- Measure cold start time on the oldest supported device. If >2s, defer initialization until after first frame.
- Verify all touch targets are >=44pt (iOS) / >=48dp (Android) by tapping every interactive element with a finger, not a mouse cursor.
- Check Dark Mode on every screen. If any text is unreadable or any element is invisible, fix before merge.
- Test with Dynamic Type (iOS) / Font Scale 200% (Android). If text truncates or overlaps, fix the layout.
- Run the app with VoiceOver (iOS) / TalkBack (Android) on the core flow. Every interactive element must be announced meaningfully.
- Check that no sensitive data (tokens, passwords, PII) appears in logs. Run
adb logcat(Android) or Console.app (iOS) during the flow.
Failure Recovery
- App crashes on launch: Check crash log for the exact line. Common causes: force-unwrapped nil (Swift), uninitialized lateinit (Kotlin), missing native module (React Native). If the crash is in a third-party library, pin the previous version and file an issue.
- Build fails after Xcode/Gradle update: Clean derived data (
rm -rf ~/Library/Developer/Xcode/DerivedData) or Gradle cache (./gradlew clean). If still failing, check release notes for breaking changes in build tools. Pin the toolchain version in the project until the issue is resolved. - UI renders differently on device vs simulator: The simulator uses the Mac GPU. Test on a real device. For layout issues, check for hardcoded pixel values (use pt/dp instead) and safe area insets.
- Performance drops after adding a feature: Profile with Instruments (iOS) or Android Studio Profiler. Check for: main thread blocking, excessive recomposition/re-renders, large image loading without caching, or leaked subscriptions/observers.
- Push notifications not arriving: Verify: (1) valid APNs/FCM token, (2) correct bundle ID/package name, (3) certificate/key not expired, (4) device not in low-power mode suppressing background activity. Test with a manual push via
curlto APNs/FCM before debugging app code. - App rejected by App Store / Play Store: Read the rejection reason exactly. Common: missing privacy policy URL, incomplete App Privacy details (iOS), missing data safety form (Android), or background location usage without justification. Fix the metadata — do not guess.
Existing App Orientation
When taking over or joining an existing mobile project:
- Build and run (15 min) — Clone, install deps, build, run on a real device. If it fails, fix the build first.
- Identify the architecture pattern (10 min) — MVC, MVVM, MVI, or unstructured? Check: where does state live? How does data flow from API to UI?
- Map the navigation graph (10 min) — List all screens and how they connect. Check for deep link handlers. Note any navigation library in use.
- Check the dependency list (5 min) — Podfile/SPM (iOS), build.gradle (Android), package.json (React Native). Flag outdated or abandoned dependencies.
- Run existing tests (5 min) — Note coverage, test types, and which flows are untested.
- Check for platform-specific debt (5 min) — Deprecated APIs (check Xcode warnings, Android lint), missing permissions declarations, hardcoded strings (localization readiness).
- Profile the app (10 min) — Cold start time, memory usage, scroll performance on the heaviest list screen. These numbers are your baseline.
Scripts
scripts/check_app_size.sh
Analyze a mobile app build output directory for size issues. Takes a build output directory as argument, finds app binaries/bundles (.app, .apk, .aab, .ipa), reports total size, largest files, and asset breakdown by category. Warns if total exceeds common thresholds (50MB for iOS, 150MB for Android).
scripts/check_app_size.sh ./build/outputs/apk/release
scripts/check_app_size.sh --threshold-ios 40 --top-files 10 ./DerivedData/Build/Products/Release-iphoneos
scripts/check_permissions.py
Extract and audit permissions from AndroidManifest.xml or Info.plist. Identifies requested permissions, flags potentially dangerous ones (CAMERA, LOCAT
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: PeterHdd
- Source: PeterHdd/agent-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.