You know the web. This post maps that knowledge onto native iOS and Android — the platforms, languages, tooling, and cross-platform options — so you can find your footing without starting from zero.
You already know how to build user interfaces (UIs). You think in components, state, and declarative rendering; you reach for a router, a data-fetching layer, a bundler, a test runner, and a continuous-integration (CI) pipeline without thinking. The good news: almost every one of those concepts has a direct counterpart on iOS and Android. The friction isn't the ideas — it's the vocabulary, the tooling, and a handful of places where the web analogy genuinely breaks.
This post is a translation dictionary. For each concern you care about on the web, it gives you the iOS-native equivalent (Swift/SwiftUI), the Android-native equivalent (Kotlin/Jetpack Compose), and the cross-platform option (React Native (RN)/Expo, with Flutter and Kotlin Multiplatform noted). Where there's no clean equivalent, it says so explicitly.
Throughout, "the web" means your current stack: Vite/Webpack, React + TypeScript, a router, React Query, Cascading Style Sheets (CSS)/Sass/CSS Modules, Vitest + Playwright, ESLint/Prettier, GitHub Actions.
Part 0 — The five mental-model shifts (read this first)
Before the tool-for-tool tables, internalise the differences that no mapping can paper over.
1. There is no Document Object Model (DOM), and no browser. On the web, the browser is a runtime you ship into. On mobile you ship a compiled binary that talks directly to the operating system (OS). There's no global document, no CSS cascade, no window. Your UI is a tree of native view objects (or, for Flutter, pixels drawn on a canvas). This is why styling is local and per-component, not global.
2. Declarative UI already won — your React instincts transfer directly. The single most important thing to know: SwiftUI, Jetpack Compose, React Native, and Flutter are all built on React's core idea. State drives the view; you describe what the UI should look like for a given state; the framework diffs and updates. If you understand useState and re-renders, you understand @State, remember, and setState. The counter example below is deliberately trivial because it should feel boringly familiar.
3. Two OSes means two of almost everything — unless you pick a cross-platform layer. Native means writing (and maintaining) the app twice: once in Swift, once in Kotlin. Cross-platform (React Native/Expo, Flutter, or shared logic via Kotlin Multiplatform) trades some native fidelity and platform-lag for one codebase. For a React developer, Expo is the natural on-ramp and is often described as "the Next.js of mobile."
4. You can't "just deploy." No git push to production. Native apps go through App Store / Play Store review, code signing, provisioning profiles, and staged rollouts. The one escape hatch is React Native's over-the-air (OTA) updates (Expo Application Services (EAS) Update), which can hot-swap the JavaScript layer without a store review — but never the native layer.
5. The OS is a hostile, resource-constrained environment. Apps get killed in the background. Permissions must be requested at runtime and can be revoked. There are real threads (not one event loop), a strict app lifecycle, memory pressure, and two entirely different design languages (Apple's Human Interface Guidelines (HIG) vs Google's Material). These have no web equivalent and are covered in Part 3.
Part 1 — The one code example you need
The same counter, five ways. Notice how little conceptual distance there is.
// React (web)
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
// SwiftUI (iOS)
struct Counter: View {
@State private var count = 0
var body: some View {
Button("\(count)") { count += 1 }
}
}
// Jetpack Compose (Android)
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Button(onClick = { count++ }) { Text("$count") }
}
// React Native (cross-platform)
function Counter() {
const [count, setCount] = useState(0);
return <Button title={`${count}`} onPress={() => setCount(count + 1)} />;
}
// Flutter (cross-platform) — inside a StatefulWidget
Widget build(BuildContext context) => TextButton(
onPressed: () => setState(() => count++),
child: Text('$count'),
);
@State / remember { mutableStateOf() } / setState are all useState. The rest of this document is mostly vocabulary on top of that shared foundation.
Part 2 — The tool-for-tool map
Each section is: a one-line framing, a comparison table, then the gotcha where the analogy leaks.
Language & type system
You're leaving TypeScript for two languages that are, honestly, nicer — statically typed, null-safe, compiled, no undefined footguns.
| Web | iOS (native) | Android (native) | Cross-platform |
|---|---|---|---|
| TypeScript / JS | Swift 6.2 | Kotlin 2.x | RN: TypeScript · Flutter: Dart |
| Structural typing | Nominal typing, value types (struct) | Nominal typing, data class | Same as web (RN) |
null/undefined | Optionals (String?) enforced by compiler | Nullable types (String?) enforced by compiler | RN keeps TS's looser model |
tsc (erased at runtime) | Types compile to machine code | Types compile to JVM/native bytecode | — |
Gotcha: Swift and Kotlin null-safety is enforced by the compiler at build time — not a lint rule you can // @ts-ignore away. Both are remarkably easy for a TS developer to read; the concepts (generics, extensions, first-class functions, sum types via enum/sealed class) all map. Dart is the outlier — a whole new language, which is part of Flutter's cost.
UI framework & rendering
| Web | iOS (native) | Android (native) | Cross-platform |
|---|---|---|---|
| React (modern) | SwiftUI (declarative) | Jetpack Compose (declarative) | RN maps JSX → real native views |
| Legacy imperative DOM | UIKit (older, imperative) | Android Views/XML (older, imperative) | Flutter draws its own pixels (Impeller/Skia) |
| Virtual DOM diffing | SwiftUI diffs its view tree | Compose recomposition | RN's Fabric renderer (New Arch) |
| Renders to DOM nodes | Renders to real UIViews | Renders to real android.view.Views | RN → real native views · Flutter → canvas |
Gotcha: The old imperative frameworks (UIKit, Android Views) still exist and underpin a huge amount of production code — treat them like jQuery-era knowledge you may need to read but shouldn't start new projects with. New native work in 2026 is SwiftUI + Compose. Note the deep divide in cross-platform: React Native renders real native components (your JSX becomes an actual UIView/View), whereas Flutter paints everything itself on a canvas — pixel-perfect and consistent, but not "native" widgets.
Components, styling & layout
The biggest adjustment. There is no stylesheet, no cascade, no className soup.
| Web | iOS (native) | Android (native) | Cross-platform |
|---|---|---|---|
| CSS / Sass / CSS Modules | View modifiers (.padding().foregroundStyle()) | Modifier chains (Modifier.padding().background()) | RN: StyleSheet objects · Flutter: widget props |
| Tailwind / utility classes | (no direct equivalent) | (no direct equivalent) | NativeWind = Tailwind for RN |
| Flexbox / Grid | VStack / HStack / ZStack + Spacer | Column / Row / Box | RN: flexbox (via Yoga) · Flutter: Row/Column/Flex |
theme / CSS variables | Environment, semantic colors | MaterialTheme, design tokens | RN: theme context/libs · Flutter: ThemeData |
@media queries | Size classes / GeometryReader | WindowSizeClass | Dimensions API / responsive libs |
Gotchas:
- Styling is local and typed. You attach appearance to each element via modifiers/props. No global stylesheet means no cascade bugs — but also no "one line in
:rootthemes the whole app" unless you build that abstraction yourself. - Layout is flexbox-ish but not flexbox in SwiftUI/Compose. Stacks resemble flex containers; you'll learn
Spacer, alignment, and frame/size modifiers instead ofjustify-content. - React Native is flexbox (it uses the Yoga engine) — but the default
flexDirectioniscolumn, notrowlike the web. This trips up every web dev at least once.
State management
Your useState → Context → Zustand/Redux → React Query ladder has a rung-for-rung equivalent.
| Web | iOS (native) | Android (native) | Cross-platform |
|---|---|---|---|
useState / useReducer | @State, @Binding | remember { mutableStateOf() } | RN: same hooks · Flutter: setState |
| Context API | @Environment / @EnvironmentObject | CompositionLocal | RN: Context · Flutter: InheritedWidget |
| Zustand / Redux / global store | @Observable class (Observation) + @State | ViewModel + StateFlow | RN: Zustand/Redux/Jotai · Flutter: Riverpod/Bloc |
| React Query / SWR (server state) | Custom @Observable + async loaders | ViewModel + repository + Flow | RN: React Query works as-is · Flutter: dio + providers |
| Redux DevTools time-travel | (no first-party equivalent) | (no first-party equivalent) | Flipper / RN DevTools |
Gotcha: On iOS, @Observable (the Observation framework, iOS 17+) replaced the older ObservableObject + @Published pattern — expect to see both in tutorials; prefer @Observable for new code. Android's canonical answer is the ViewModel + StateFlow pairing (a ViewModel survives configuration changes like screen rotation — a concept with no web equivalent). Crucially, in React Native your entire web state stack — Zustand, Redux, React Query — runs unchanged, which is a major reason RN feels like home.
Navigation & routing
| Web | iOS (native) | Android (native) | Cross-platform |
|---|---|---|---|
| React Router / Next App Router | NavigationStack + typed paths | Navigation Compose / Navigation 3 | RN: Expo Router (file-based!) or React Navigation · Flutter: go_router |
<Link> / router.push() | NavigationLink / path append | navController.navigate() | Expo <Link> / router.push() |
| Nested layouts | Tab bars + nav stacks | Scaffold + nav graph | Expo Router layouts (_layout files) |
| URL params / query | Typed navigation values | Typed nav arguments | File-based route params |
Deep links (/product/42) | Universal Links / URL schemes | App Links / intent filters | Expo deep-linking config |
Gotcha: Native navigation is a literal stack you push and pop, plus platform chrome (iOS nav bars with swipe-back, Android's back button/gesture and system back stack). There's no uniform resource locator (URL) bar. Expo Router will feel uncanny to you: it's file-based routing (app/product/[id].tsx), typed params, and layouts — essentially the Next.js App Router model ported to mobile. If you know App Router, you already know Expo Router.
Async & concurrency
| Web | iOS (native) | Android (native) | Cross-platform |
|---|---|---|---|
Promise / async/await | Swift Concurrency: async/await | Coroutines: suspend fun | RN: same JS model · Flutter: Future |
| Single event loop | Real threads + actors for safety | Real threads + structured concurrency | RN: JS thread + native threads |
Promise.all | async let / task groups | async {} / awaitAll | Promise.all (RN) |
| Observables / RxJS | AsyncSequence, Combine | Flow / StateFlow | RxJS (RN) · Stream (Flutter) |
| — (UI is single-threaded) | Must hop to @MainActor for UI | Must switch to Dispatchers.Main | RN marshals to JS/UI thread |
Gotcha: This is where "no event loop" bites. There are real threads, so you must explicitly move work back to the main thread before touching UI. Swift 6.2's "approachable concurrency" (main-actor-by-default for UI code) and Kotlin's structured coroutines make this ergonomic, but the compiler will now catch data races that JavaScript's single-threaded model never let you have. Kotlin's Flow/StateFlow ≈ RxJS Observables; Swift's AsyncSequence and Combine play the same role.
Data fetching & networking
| Web | iOS (native) | Android (native) | Cross-platform |
|---|---|---|---|
fetch / axios | URLSession (async/await) | Retrofit / Ktor / OkHttp | RN: fetch works · Flutter: http / dio |
res.json() (untyped) | Codable (typed decode) | kotlinx.serialization (typed) | RN: JSON.parse · Flutter: json_serializable |
| React Query caching | Manual or third-party | Repository pattern + Room cache | React Query (RN) |
| WebSocket | URLSessionWebSocketTask | OkHttp/Ktor WebSockets | Same as web (RN) |
| GraphQL (Apollo/urql) | Apollo iOS | Apollo Kotlin | Apollo Client (RN) |
Gotcha: JavaScript Object Notation (JSON) decoding is typed and checked on native — Codable and kotlinx.serialization turn network responses into strongly-typed models at the boundary, so you don't carry any through your app. In React Native, your existing fetch + React Query workflow ports over almost verbatim.
Local storage & persistence
| Web | iOS (native) | Android (native) | Cross-platform |
|---|---|---|---|
localStorage (small K/V) | UserDefaults | DataStore (SharedPreferences = legacy) | RN: AsyncStorage / MMKV · Flutter: shared_preferences |
| Secure secrets | Keychain | EncryptedDataStore / Keystore | RN: expo-secure-store · Flutter: flutter_secure_storage |
| IndexedDB / SQL.js | SwiftData (Core Data = legacy) | Room (SQLite ORM) | RN: expo-sqlite/WatermelonDB · Flutter: Drift/Hive |
| Cache API | URLCache / file system | OkHttp cache / file system | File-system libs |
| Cookies | HTTPCookieStorage | CookieManager | Managed by fetch layer |
Gotcha: The web treats "storage" as one blurry bucket; native forces a clean split — key-value (UserDefaults/DataStore) for small preferences vs a real embedded SQLite database (SwiftData/Room) for structured data. SwiftData (iOS 17+) replaced Core Data for new projects; Room is Android's typed SQLite object-relational mapper (ORM). Secrets go in the platform's secure enclave (Keychain / Keystore) — never in plain key-value.
Build tooling & bundler
| Web | iOS (native) | Android (native) | Cross-platform |
|---|---|---|---|
| Vite / Webpack / esbuild | Xcode build system / xcodebuild | Gradle (Android Gradle Plugin) | RN: Metro bundler · Flutter: flutter build |
vite build → JS bundle | Compile → machine code (.ipa) | Compile → bytecode/native (.aab) | RN: JS bundle + Hermes bytecode |
| HMR / dev server | SwiftUI Previews / live rebuild | Compose Preview / Live Edit | Metro + Fast Refresh · Flutter hot reload |
| Tree-shaking / code-split | Dead-code stripping (linker) | R8 shrinking/minification | Metro splitting |
Gotcha: You're compiling to a binary, not shipping source. Gradle (Android) is powerful but famously slow and configured in a Kotlin/Groovy domain-specific language (DSL) — budget time to learn it. Xcode's build system is more opaque and driven by the integrated development environment (IDE). React Native still ships a JS bundle, but the New Architecture (mandatory as of RN 0.83 / Expo SDK 55) precompiles it to Hermes bytecode for fast startup.
Package manager & registry
| Web | iOS (native) | Android (native) | Cross-platform |
|---|---|---|---|
| npm / pnpm / yarn | Swift Package Manager (CocoaPods = declining) | Gradle deps (Maven Central) | RN: npm (+ native autolinking) · Flutter: pub (pub.dev) |
package.json | Package.swift / project settings | build.gradle.kts | package.json / pubspec.yaml |
npm install | SPM resolve / pod install | Gradle sync | npm install + pod install (iOS) |
| Lockfile | Package.resolved | gradle.lockfile (opt-in) | package-lock.json / pubspec.lock |
Gotcha: Swift Package Manager (SPM) is now the primary iOS package manager; CocoaPods still appears in older projects but is winding down. In React Native you still npm install, but native modules require a build step (and, on iOS, pod install) — dependencies aren't pure JavaScript, so "it installed" doesn't always mean "it linked."
Testing
Your unit → component → end-to-end (e2e) pyramid maps cleanly.
| Web | iOS (native) | Android (native) | Cross-platform |
|---|---|---|---|
| Vitest / Jest (unit) | Swift Testing / XCTest | JUnit / kotlin-test | Jest / Vitest (RN) · flutter_test |
| Testing Library (component) | SwiftUI ViewInspector / snapshot | Compose UI test | React Native Testing Library |
| Playwright / Cypress (e2e) | XCUITest | Espresso / UI Automator | Maestro / Detox · integration_test |
| MSW (network mocking) | URLProtocol stubs | MockWebServer / MockK | MSW (RN) |
vi.mock | Protocol-based fakes | MockK / Mockito | Jest mocks |
Gotcha: "Swift Testing" is Apple's newer, expressive test framework (@Test, #expect) that sits alongside the older XCTest — you'll meet both. For cross-platform e2e, Maestro has largely become the pleasant modern choice (Detox is the older RN-specific option). Your instinct to keep e2e thin and fast applies doubly on mobile, where UI tests run on slow simulators/emulators.
Linting & formatting
| Web | iOS (native) | Android (native) | Cross-platform |
|---|---|---|---|
| ESLint | SwiftLint | detekt | ESLint (RN) · dart analyze |
| Prettier | SwiftFormat | ktlint | Prettier (RN) · dart format |
eslint --fix | SwiftFormat autofix | ktlint -F | same |
Gotcha: Nearly a one-to-one map — set these up on day one exactly as you would on the web, wired into pre-commit hooks and CI.
Dev tooling, debugging & hot reload
| Web | iOS (native) | Android (native) | Cross-platform |
|---|---|---|---|
| Chrome DevTools | Xcode debugger / LLDB | Android Studio debugger | RN DevTools / Flipper · Flutter DevTools |
| React DevTools (tree) | SwiftUI view debugger | Layout Inspector | React DevTools (RN) |
| Performance/Network tab | Instruments (profiler) | Android Studio Profiler | Network inspector / Flipper |
| HMR / Fast Refresh | SwiftUI Previews | Compose Preview + Live Edit | Fast Refresh · Flutter hot reload |
| Lighthouse | Instruments (Time Profiler, Leaks) | Profiler + Baseline Profiles | — |
Gotcha: The IDE is not optional on native — Xcode is mandatory for iOS (and macOS-only; you cannot build/ship iOS apps from Windows or Linux), and Android Studio is strongly expected for Android. SwiftUI Previews / Compose Preview render your component in isolation like a live Storybook, which is delightful. Flutter's hot reload and RN's Fast Refresh preserve state on save, matching your web hot-module-replacement (HMR) experience closely.
CI/CD & distribution
This is the biggest workflow shock — your continuous-integration/continuous-delivery (CI/CD) pipeline now ends at a store review, not a deploy.
| Web | iOS (native) | Android (native) | Cross-platform |
|---|---|---|---|
| GitHub Actions | Xcode Cloud / Fastlane | Fastlane / Gradle | EAS Build (Expo) · Codemagic |
| Vercel / Netlify (push-to-deploy) | TestFlight → App Store review | Play Console tracks → review | EAS Submit |
| Preview deployments | TestFlight builds | Internal/closed/open testing tracks | EAS internal distribution |
| Instant rollback | New build + review | New build + review | EAS Update (OTA) for JS layer |
| — | Code signing / provisioning profiles | App signing / keystore | Managed by EAS |
Gotchas:
- No instant deploy. Both stores review your app (hours to a day+). You manage certificates, provisioning profiles (iOS) and signing keys/keystores (Android) — lose these and you can lock yourself out of updating your own app. Fastlane automates most of this pain and is worth learning early.
- The one web-like escape hatch: React Native's EAS Update ships JavaScript/asset changes over-the-air, reaching users in minutes without a store review — perfect for hotfixes. It cannot change native code. This is the closest thing to "push to deploy" in mobile.
Whether a change reaches users in minutes or days depends entirely on what you changed:
Design systems & component libraries
| Web | iOS (native) | Android (native) | Cross-platform |
|---|---|---|---|
| MUI / Chakra / shadcn | Native SwiftUI components | Material 3 components | RN Paper / Tamagui / NativeWind |
| Heroicons / Lucide | SF Symbols | Material Symbols | Icon libs (@expo/vector-icons) |
| Tailwind | (build your own tokens) | MaterialTheme tokens | NativeWind (Tailwind) |
| Storybook | Xcode Previews | Compose Preview | Storybook for RN |
Gotcha: Apple and Google each ship a complete design language (Apple's HIG, Google's Material 3) baked into their frameworks — using native components gets you correct platform behaviour and accessibility for free. Cross-platform frameworks must either mimic these (Flutter ships both Material and Cupertino widget sets) or let you bring Tailwind-style utilities (NativeWind).
Part 3 — Where there is no web equivalent
These have no clean mapping. Budget real learning time.
- Permissions. Camera, location, notifications, contacts — each must be requested at runtime, can be denied or later revoked in Settings, and your UI must handle every state gracefully. Nothing on the web works quite like this.
- App lifecycle & background execution. Apps get suspended and killed by the OS. There's a formal lifecycle (foreground/background/terminated), and background work is tightly restricted and battery-policed. Long-running tasks need specific platform application programming interfaces (APIs).
- Push notifications. The Apple Push Notification service (APNs) and Firebase Cloud Messaging (FCM, on Android) — a whole subsystem with tokens, server-side sending, and permission prompts. No web analogue beyond limited Web Push.
- Native capabilities. Biometrics (Face ID / fingerprint), Bluetooth, near-field communication (NFC), camera/photo pipelines, secure enclaves, haptics, sensors — each is a native API surface.
- Code signing & provisioning. Certificates, provisioning profiles, entitlements, keystores. There's no
.envequivalent; this is cryptographic identity that gates whether your app can even run on a device or ship. - Two design languages. iOS users expect HIG conventions (swipe-back, bottom tabs, SF Symbols); Android expects Material (system back, floating action buttons (FABs), Material motion). "One design" often feels wrong on one platform.
- Device fragmentation & memory limits. Especially Android — thousands of devices, screen sizes, OS versions, and low-RAM hardware. Your app can be killed for using too much memory. Testing on real low-end devices matters.
- No hot-swapping native code in production. Web ships new JS on every load. Native ships a reviewed binary. (RN's OTA covers only the JS layer.)
Part 4 — Which path should a React developer take?
| Your situation | Recommended path | Why |
|---|---|---|
| React dev, want to ship mobile fast, standard app | Expo + React Native | Reuse React, TS, React Query, your mental model. Expo Router ≈ Next.js App Router. OTA updates. Lowest ramp for you. |
| Need max native fidelity / heavy platform APIs / games | Native (Swift + Kotlin) | Day-one access to new OS features, best performance, no bridge. Cost: two codebases + two languages. |
| Want one UI codebase incl. web/desktop, OK learning Dart | Flutter | Single rendering engine everywhere, excellent tooling. Cost: Dart, non-native widgets, larger binaries. |
| Have (or want) shared business logic, native UIs | Kotlin Multiplatform (KMP) | Share models/networking/logic in Kotlin; keep SwiftUI + Compose for UI. Production-proven in 2026, but Kotlin-centric. |
The same four options, seen as a trade-off between native fidelity (how close it feels to a hand-built platform app) and how much code you share across iOS and Android:
For you specifically — a React/Next.js/TypeScript developer — the pragmatic on-ramp is Expo. It lets you carry ~90% of your existing stack (React, TypeScript, React Query, Zustand, your testing habits) and learn mobile concepts (navigation stacks, permissions, lifecycle, store deployment) without simultaneously learning two new languages. Once you've shipped, you'll understand the native layer well enough to decide whether any given feature justifies dropping into Swift/Kotlin.
A suggested learning sequence
- Ship a small Expo app with Expo Router, a couple of screens, a
fetch+ React Query data layer, andexpo-secure-store. This teaches mobile concepts on familiar tools. - Add one native capability (camera, notifications, biometrics) to feel the permissions + lifecycle model.
- Do a real store submission via EAS — TestFlight and a Play internal-testing track. This demystifies signing and review.
- Then, if curious, build the same tiny screen natively — a SwiftUI view and a Compose screen. The counter from Part 1 is your "hello world." You'll be surprised how familiar
@Stateandrememberfeel. - Only after that evaluate whether a native or KMP investment is worth it for your product.
The same on-ramp at a glance:
One-screen cheat sheet
| Concern | Web | iOS | Android | Cross-platform |
|---|---|---|---|---|
| Language | TS | Swift | Kotlin | TS (RN) / Dart (Flutter) |
| UI | React | SwiftUI | Compose | RN / Flutter |
| Styling | CSS/Sass | modifiers | Modifier | StyleSheet/NativeWind |
| Layout | Flexbox | VStack/HStack | Column/Row | Yoga flexbox |
| Local state | useState | @State | remember | useState / setState |
| App state | Zustand/Redux | @Observable | ViewModel+StateFlow | Zustand (RN) / Riverpod |
| Server state | React Query | async loaders | repo + Flow | React Query (RN) |
| Routing | React Router/Next | NavigationStack | Navigation Compose | Expo Router / go_router |
| Async | async/await | Swift Concurrency | Coroutines/Flow | JS async / Future |
| Networking | fetch/axios | URLSession | Retrofit/Ktor | fetch / dio |
| K/V storage | localStorage | UserDefaults | DataStore | AsyncStorage/MMKV |
| DB | IndexedDB | SwiftData | Room | expo-sqlite / Drift |
| Build | Vite/Webpack | Xcode | Gradle | Metro / flutter build |
| Packages | npm | SPM | Gradle/Maven | npm / pub |
| Unit test | Vitest/Jest | Swift Testing | JUnit | Jest / flutter_test |
| E2E test | Playwright | XCUITest | Espresso | Maestro / integration_test |
| Lint/format | ESLint/Prettier | SwiftLint/SwiftFormat | detekt/ktlint | ESLint / dart |
| Hot reload | HMR | SwiftUI Previews | Compose Preview | Fast Refresh / hot reload |
| CI/CD | GH Actions | Xcode Cloud/Fastlane | Fastlane/Gradle | EAS Build |
| Deploy | Vercel push | TestFlight→App Store | Play Console | EAS Submit + OTA |
State-of-the-ecosystem note (mid-2026): iOS figures assume Swift 6.2, SwiftUI with @Observable/SwiftData, and NavigationStack. Android assumes Kotlin 2.x, Jetpack Compose with Navigation 3, ViewModel + StateFlow, and Room/DataStore. Cross-platform assumes React Native 0.83+/Expo SDK 55 with the New Architecture mandatory and Hermes as the default engine, plus Flutter (Dart) and Kotlin Multiplatform + Compose Multiplatform as alternatives. Framework defaults and deprecations move fast — always cross-check the official release notes for your exact minor version.
