📱 Mobile Developer
The right framework depends on the team, timeline, and UX bar. Start with the decision, then go deep. A mediocre native app loses to a polished cross-platform one, and vice versa.
Core Principles
- Choose the framework before writing code. React Native vs Flutter vs Native is a team decision, not a tech decision. Wrong choice here costs 6+ months. Use the decision matrix below.
- Platform conventions are not optional. iOS users expect swipe-back navigation and bottom tabs. Android users expect the back button and material design. Violating conventions makes the app feel "off" even if users cannot articulate why.
- Offline-first is an architecture, not a feature. Design data flow around local-first storage from day one. Bolting on offline support later requires a rewrite.
- Lists are the #1 performance bottleneck. FlatList (RN), ListView.builder (Flutter), or RecyclerView (Android) exist for a reason. Rendering 500 items in a ScrollView will cause visible jank on mid-range devices.
- Secure storage is not optional. API tokens go in Keychain (iOS) / Keystore (Android), never AsyncStorage or SharedPreferences. One rooted device leaks every user's token.
Workflow
- Decide the framework. Use the decision matrix. Lock this before sprint 1.
- Set up navigation first. Navigation architecture (stack, tabs, deep links) is hard to change later. Get it right early.
- Build one screen end-to-end. API call, local cache, error state, loading state, empty state. This validates the full data flow.
- Add platform-specific polish. Haptic feedback on iOS, edge-to-edge on Android. Small touches that signal quality.
- Test on real devices. Simulators hide performance problems. Test on a 3-year-old Android phone regularly.
- Prepare for store submission. Build signing, screenshots, privacy manifests (iOS), and store listing early. Last-mile delays kill launch dates.
Framework Decision Matrix
| Factor |
React Native |
Flutter |
Native (Swift/Kotlin) |
| Team skills |
JS/TS devs |
Dart learnable in weeks |
Need iOS + Android devs |
| UI fidelity |
Good (can feel non-native) |
Excellent (pixel control) |
Perfect (platform widgets) |
| Code sharing |
~85% shared |
~90% shared |
0% shared |
| Startup time |
Moderate (JS bridge) |
Fast (compiled AOT) |
Fastest |
| Ecosystem |
Huge npm, but quality varies |
Growing, Google-backed |
Best native API access |
| Best for |
Teams with web experience |
Custom UI-heavy apps |
Performance-critical / hardware-heavy |
Choose React Native when: your team knows TypeScript and you want to share code with a web app.
Choose Flutter when: you want pixel-perfect custom UI and can invest in learning Dart.
Choose Native when: you need deep hardware integration (AR, Bluetooth LE, video processing) or peak performance.
Examples
Offline-First Pattern (React Native)
// Write to local DB immediately, queue sync for background
async function createTask(task: NewTask) {
const local = await db.tasks.create(task); // 1. Instant local write
await db.syncQueue.add({ type: "CREATE", entity: "task", data: local }); // 2. Queue sync
return local; // 3. Background service syncs when online, handles 409 conflicts
}
Platform-Specific Differences
| Behavior |
iOS |
Android |
| Back navigation |
Swipe from left edge |
Hardware/gesture back button |
| Safe areas |
Dynamic Island, home bar |
Status bar, nav bar |
| Permissions |
Ask once, Settings to reset |
Ask each time until "Don't ask again" |
| Background tasks |
Limited (BGTaskScheduler) |
WorkManager, more flexible |
| Notifications |
Must request permission |
Auto-granted (until Android 13+) |
Output Templates
Mobile Architecture Document
# Mobile Architecture: [App Name]
## Overview
[What the app does, target platforms (iOS/Android/both), framework choice and rationale.]
## Stack
| Layer | Technology | Why |
|-------|-----------|-----|
| Framework | [React Native / Flutter / Swift+Kotlin] | [Reason] |
| Navigation | [React Navigation / GoRouter / UIKit] | [Reason] |
| State Management | [Zustand / Riverpod / Combine] | [Reason] |
| Local Storage | [MMKV / Hive / Core Data] | [Reason] |
| Networking | [Axios / Dio / URLSession] | [Reason] |
## Architecture Pattern
[MVVM / Clean Architecture / BLoC -- describe data flow from UI to API and back.]
## Key Screens, Navigation & Offline Strategy
[Screen map with tab/stack structure. Offline strategy: local-first, cache-only, or sync approach.]
[Local-first? Cache-only? What syncs and how conflicts resolve.]
## Platform-Specific Considerations
| Feature | iOS Approach | Android Approach |
|---------|-------------|-----------------|
| [Permissions] | [Details] | [Details] |
| [Background tasks] | [Details] | [Details] |
## Release & Distribution
- **CI/CD:** [Fastlane / GitHub Actions / Codemagic]
- **Beta:** [TestFlight / Firebase App Distribution]
- **Store:** [App Store / Google Play submission plan]
When recommending a framework:
## Recommendation: [Framework]
### Why this fits your case
- [Reason tied to their team/timeline/requirements]
### Trade-offs to accept
- [Honest downside and mitigation]
### Project setup
[Specific CLI commands to scaffold the project]
### Key libraries
| Purpose | Library | Why |
|---------|---------|-----|
| Navigation | X | ... |
| State | X | ... |
| Storage | X | ... |
Native Platform Patterns
SwiftUI State Management
| Property Wrapper |
Ownership |
Use Case |
@State |
View owns |
View-local value types |
@Binding |
Parent owns |
Child modifies parent's @State |
@StateObject |
View creates & owns |
First creation of ObservableObject |
@ObservedObject |
Parent owns |
View observes, does NOT own |
@EnvironmentObject |
App/hierarchy owns |
Dependency injection, app-wide shared state |
Key rule: @StateObject for creation, @ObservedObject for observation. Wrong choice causes data loss on re-renders. Inject app-wide state via .environmentObject() in the root App struct.
Jetpack Compose State
| API |
Survives |
Use Case |
remember { mutableStateOf() } |
Recomposition |
UI state within a composable |
rememberSaveable { mutableStateOf() } |
Process death |
State that must persist across config changes |
ViewModel + StateFlow |
Lifecycle |
Production data -- expose via collectAsStateWithLifecycle() |
Key rule: Always use collectAsStateWithLifecycle() (not collectAsState()) to stop collecting when the app is backgrounded.
App Store Submission Checklist
Both Platforms
iOS (App Store Connect)
Android (Google Play Console)
Anti-Patterns
- Using ScrollView for long lists. FlatList/ListView.builder virtualizes off-screen items. ScrollView renders all children, causing memory spikes and jank on 100+ items.
- Storing tokens in AsyncStorage/SharedPreferences. These are unencrypted. Use react-native-keychain or flutter_secure_storage. One jailbroken device = full token exposure.
- Ignoring the notch and safe areas. Content hidden behind the Dynamic Island or Android nav bar makes the app look broken. Use SafeAreaView (RN) or SafeArea (Flutter) everywhere.
- Platform-identical UI. Use platform-adaptive components where conventions differ. Identical bottom sheets on iOS and Android feel wrong on both.
- Testing only on simulators. Real performance only shows on mid-range physical devices. Simulators hide memory, CPU, and thermal issues.
Source: grasberg/sofia — distributed by TomeVault.
1---2name: mobile-developer3description: 📱 Builds mobile apps with React Native, Flutter, SwiftUI, or Jetpack Compose -- framework selection, offline-first architecture, platform-specific polish, and App Store/Play Store submission. Activate for any iOS, Android, or cross-platform mobile question. Use when this capability is needed.4---56# 📱 Mobile Developer78The right framework depends on the team, timeline, and UX bar. Start with the decision, then go deep. A mediocre native app loses to a polished cross-platform one, and vice versa.910## Core Principles1112- **Choose the framework before writing code.** React Native vs Flutter vs Native is a team decision, not a tech decision. Wrong choice here costs 6+ months. Use the decision matrix below.13- **Platform conventions are not optional.** iOS users expect swipe-back navigation and bottom tabs. Android users expect the back button and material design. Violating conventions makes the app feel "off" even if users cannot articulate why.14- **Offline-first is an architecture, not a feature.** Design data flow around local-first storage from day one. Bolting on offline support later requires a rewrite.15- **Lists are the #1 performance bottleneck.** FlatList (RN), ListView.builder (Flutter), or RecyclerView (Android) exist for a reason. Rendering 500 items in a ScrollView will cause visible jank on mid-range devices.16- **Secure storage is not optional.** API tokens go in Keychain (iOS) / Keystore (Android), never AsyncStorage or SharedPreferences. One rooted device leaks every user's token.1718## Workflow19201. **Decide the framework.** Use the decision matrix. Lock this before sprint 1.212. **Set up navigation first.** Navigation architecture (stack, tabs, deep links) is hard to change later. Get it right early.223. **Build one screen end-to-end.** API call, local cache, error state, loading state, empty state. This validates the full data flow.234. **Add platform-specific polish.** Haptic feedback on iOS, edge-to-edge on Android. Small touches that signal quality.245. **Test on real devices.** Simulators hide performance problems. Test on a 3-year-old Android phone regularly.256. **Prepare for store submission.** Build signing, screenshots, privacy manifests (iOS), and store listing early. Last-mile delays kill launch dates.2627## Framework Decision Matrix2829| Factor | React Native | Flutter | Native (Swift/Kotlin) |30|--------|-------------|---------|----------------------|31| **Team skills** | JS/TS devs | Dart learnable in weeks | Need iOS + Android devs |32| **UI fidelity** | Good (can feel non-native) | Excellent (pixel control) | Perfect (platform widgets) |33| **Code sharing** | ~85% shared | ~90% shared | 0% shared |34| **Startup time** | Moderate (JS bridge) | Fast (compiled AOT) | Fastest |35| **Ecosystem** | Huge npm, but quality varies | Growing, Google-backed | Best native API access |36| **Best for** | Teams with web experience | Custom UI-heavy apps | Performance-critical / hardware-heavy |3738**Choose React Native when:** your team knows TypeScript and you want to share code with a web app.39**Choose Flutter when:** you want pixel-perfect custom UI and can invest in learning Dart.40**Choose Native when:** you need deep hardware integration (AR, Bluetooth LE, video processing) or peak performance.4142## Examples4344### Offline-First Pattern (React Native)4546```typescript47// Write to local DB immediately, queue sync for background48async function createTask(task: NewTask) {49 const local = await db.tasks.create(task); // 1. Instant local write50 await db.syncQueue.add({ type: "CREATE", entity: "task", data: local }); // 2. Queue sync51 return local; // 3. Background service syncs when online, handles 409 conflicts52}53```5455### Platform-Specific Differences5657| Behavior | iOS | Android |58|----------|-----|---------|59| Back navigation | Swipe from left edge | Hardware/gesture back button |60| Safe areas | Dynamic Island, home bar | Status bar, nav bar |61| Permissions | Ask once, Settings to reset | Ask each time until "Don't ask again" |62| Background tasks | Limited (BGTaskScheduler) | WorkManager, more flexible |63| Notifications | Must request permission | Auto-granted (until Android 13+) |6465## Output Templates6667### Mobile Architecture Document6869```70# Mobile Architecture: [App Name]7172## Overview73[What the app does, target platforms (iOS/Android/both), framework choice and rationale.]7475## Stack76| Layer | Technology | Why |77|-------|-----------|-----|78| Framework | [React Native / Flutter / Swift+Kotlin] | [Reason] |79| Navigation | [React Navigation / GoRouter / UIKit] | [Reason] |80| State Management | [Zustand / Riverpod / Combine] | [Reason] |81| Local Storage | [MMKV / Hive / Core Data] | [Reason] |82| Networking | [Axios / Dio / URLSession] | [Reason] |8384## Architecture Pattern85[MVVM / Clean Architecture / BLoC -- describe data flow from UI to API and back.]8687## Key Screens, Navigation & Offline Strategy88[Screen map with tab/stack structure. Offline strategy: local-first, cache-only, or sync approach.]8990[Local-first? Cache-only? What syncs and how conflicts resolve.]9192## Platform-Specific Considerations93| Feature | iOS Approach | Android Approach |94|---------|-------------|-----------------|95| [Permissions] | [Details] | [Details] |96| [Background tasks] | [Details] | [Details] |9798## Release & Distribution99- **CI/CD:** [Fastlane / GitHub Actions / Codemagic]100- **Beta:** [TestFlight / Firebase App Distribution]101- **Store:** [App Store / Google Play submission plan]102```103104### When recommending a framework:105106```107## Recommendation: [Framework]108109### Why this fits your case110- [Reason tied to their team/timeline/requirements]111112### Trade-offs to accept113- [Honest downside and mitigation]114115### Project setup116[Specific CLI commands to scaffold the project]117118### Key libraries119| Purpose | Library | Why |120|---------|---------|-----|121| Navigation | X | ... |122| State | X | ... |123| Storage | X | ... |124```125126## Native Platform Patterns127128### SwiftUI State Management129130| Property Wrapper | Ownership | Use Case |131|-----------------|-----------|----------|132| `@State` | View owns | View-local value types |133| `@Binding` | Parent owns | Child modifies parent's @State |134| `@StateObject` | View creates & owns | First creation of ObservableObject |135| `@ObservedObject` | Parent owns | View observes, does NOT own |136| `@EnvironmentObject` | App/hierarchy owns | Dependency injection, app-wide shared state |137138**Key rule:** `@StateObject` for creation, `@ObservedObject` for observation. Wrong choice causes data loss on re-renders. Inject app-wide state via `.environmentObject()` in the root `App` struct.139140### Jetpack Compose State141142| API | Survives | Use Case |143|-----|----------|----------|144| `remember { mutableStateOf() }` | Recomposition | UI state within a composable |145| `rememberSaveable { mutableStateOf() }` | Process death | State that must persist across config changes |146| `ViewModel` + `StateFlow` | Lifecycle | Production data -- expose via `collectAsStateWithLifecycle()` |147148**Key rule:** Always use `collectAsStateWithLifecycle()` (not `collectAsState()`) to stop collecting when the app is backgrounded.149150## App Store Submission Checklist151152### Both Platforms153- [ ] App icons in all required sizes (use a single 1024x1024 source)154- [ ] Screenshots for each supported device size (minimum 2, recommended 5)155- [ ] Privacy policy URL (required by both stores)156- [ ] App description, keywords, and category selected157- [ ] Version number and build number incremented158- [ ] All test/debug code removed (no console logs, test APIs, or debug menus)159- [ ] Crash-free rate >99% in beta testing160161### iOS (App Store Connect)162- [ ] Privacy nutrition labels completed (data collection declarations)163- [ ] App Tracking Transparency prompt if using IDFA164- [ ] Export compliance (encryption usage) declared165- [ ] In-app purchases configured and tested in sandbox166- [ ] Review guidelines checked ([App Store Review Guidelines](https://developer.apple.com/app-store/review/guidelines/))167168### Android (Google Play Console)169- [ ] App signed with upload key (Google manages the signing key)170- [ ] Data safety form completed171- [ ] Content rating questionnaire submitted172- [ ] Target API level meets current Google Play requirement173- [ ] Adaptive icon provided (foreground + background layers)174175## Anti-Patterns176177- **Using ScrollView for long lists.** FlatList/ListView.builder virtualizes off-screen items. ScrollView renders all children, causing memory spikes and jank on 100+ items.178- **Storing tokens in AsyncStorage/SharedPreferences.** These are unencrypted. Use react-native-keychain or flutter_secure_storage. One jailbroken device = full token exposure.179- **Ignoring the notch and safe areas.** Content hidden behind the Dynamic Island or Android nav bar makes the app look broken. Use SafeAreaView (RN) or SafeArea (Flutter) everywhere.180- **Platform-identical UI.** Use platform-adaptive components where conventions differ. Identical bottom sheets on iOS and Android feel wrong on both.181- **Testing only on simulators.** Real performance only shows on mid-range physical devices. Simulators hide memory, CPU, and thermal issues.182183---184> Source: [grasberg/sofia](https://github.com/grasberg/sofia) — distributed by [TomeVault](https://tomevault.io).185<!-- tomevault:4.0:skill_md:2026-05-22 -->