Raxx · internal docs

internal · gated

ADR 0140 — Android and Mobile Portfolio Strategy

Status: Proposed — awaiting operator decision on approach (see §Open questions) Date: 2026-07-13 UTC Deciders: software-architect (analysis); operator (final decision) Scope: Raxx mobile portfolio — iOS app, prospective Android app, and the long-term maintenance model for both


Context

Raxx today has:

The operator is considering adding Android. This forces a strategic choice about the mobile portfolio architecture that is effectively irreversible once chosen: the iOS codebase investment, the billing implementations, and the passkey credential registrations all carry migration costs that compound with user growth.

This ADR is a decision-support document. It does not commit to building Android — it defines the fork, the constraints that bear on it, and a recommendation.


Invariants

The following TradeMasterAPI-level invariants constrain every option:


The Three Options

Option A — Native Both

Keep SwiftUI iOS. Add native Kotlin / Jetpack Compose for Android.

Option B — Cross-Platform Unification

Replace the SwiftUI iOS app with a single cross-platform framework covering both iOS and Android. Sub-options: React Native (B-RN), Flutter (B-FL), or Kotlin Multiplatform (B-KMP).

Option C — Web Wrapper

Wrap the existing Next.js web app in a store-distributable shell. - iOS: already rejected by ADR-0116. - Android: Trusted Web Activity (TWA) or Capacitor (Android only).


Options Matrix

Dimension A — Native Both B-RN — React Native B-KMP — KMP + SwiftUI/Compose B-FL — Flutter C — TWA (Android only)
iOS investment preserved? Yes, fully No — rewrite required Yes (SwiftUI views stay; shared layer added) No — rewrite required Yes
Code reuse with Next.js None (mobile is already separate) Hooks/utils portable in theory; no component reuse (different renderer) None (Kotlin shared logic; Next.js is TypeScript) None High — same codebase serves web + Android
Time-to-Android (eng-weeks) 8–12 wks 16–22 wks (iOS rewrite + Android) 12–16 wks (shared layer + Compose) 18–24 wks (iOS + Android rewrite, new ecosystem) 4–6 wks
iOS regression risk None High — full SwiftUI replacement Low — iOS views stay High — full replacement None
Passkeys — iOS AuthenticationServices (done) Community lib; already rejected ADR-0116 AuthenticationServices (done) Community plugin (same risk as RN) N/A (iOS unchanged)
Passkeys — Android Android Credential Manager API (native, FIDO2, Jetpack) react-native-passkeys (community) Android Credential Manager (native) passkeys flutter plugin (community) Chrome handles WebAuthn natively in TWA (not a webview)
Apple IAP StoreKit 2 (done) Community RN bridge StoreKit 2 via Swift interop layer community plugin N/A (iOS unchanged)
Google Play Billing Native Play Billing library (Kotlin) RevenueCat or react-native-purchases Native Play Billing (Kotlin) in_app_purchase pub.dev plugin Digital Goods API (Chrome 101+; functional but non-trivial to implement)
Maintenance burden (solo) High — 3 codebases; mobile apps are thin and structurally parallel Medium — 2 codebases; community-bridge maintenance adds latent risk Medium — 3 targets, shared logic reduces duplication; build tooling is complex Medium — 2 codebases; Dart adds a third language Low — 2 codebases; Android improvements = web improvements
App Store quality bar Full native on both stores Full native appearance; some bridge jank possible Full native on both stores Full native appearance TWA must pass Lighthouse PWA checks; perceived as web app by many users
Solo-founder long-term risk Two thin native mobile codebases; manageable given structural parallelism Community libraries on auth + billing critical paths Complex Gradle + Xcode dual build; KMP v1.9.x still maturing Dart is a fourth language (TS/Python/Swift + Dart); no reuse with any existing codebase Binds Android quality to web PWA maturity; Play Billing via Digital Goods API is functional but less proven than native

Detailed Constraint Analysis

Passkeys

The RP ID raxx.app works natively on Android via the Android Credential Manager API (Jetpack, API level 28+, covering ~97% of active Android devices). The setup is analogous to iOS:

For Option C (TWA): Chrome on Android handles WebAuthn natively within the TWA process. This is not the same as an in-app browser. The feedback_passkey_fails_in_inapp_browsers incident involved Gmail's Chrome Custom Tab running in a reduced-permission context — TWA runs as a first-class Chrome activity and has full navigator.credentials access, including passkeys. This is a genuine passkey-viable path for Android.

For Options B-RN and B-FL: the passkey path routes through a community plugin. ADR-0116 rejected this posture for iOS. The same reasoning applies to Android: a financial app gating live trading behind passkey auth should not depend on a community-maintained bridge to the credential API. If that bridge goes unmaintained, auth breaks.

Billing

iOS (all options that keep iOS native): Apple IAP via StoreKit 2 is locked (ADR-0007). The existing StoreKitManager.swift is unaffected by the Android decision.

Android billing by option:

Existing iOS Investment Assessment

The iOS app at ios/Raxx/ contains 20 Swift files. The load-bearing integrations:

TestFlight Wave 2 is the specific sunk-cost signal: this is not a prototype, it is a beta-validated app. Replacing it with a community-bridge framework resets beta stability.

Next.js Code Reuse

React components do not run in React Native (different renderer). No component code from raxx-next is mechanically portable to React Native, Flutter, or KMP. Business logic patterns (API call structure, state shape, route guards) inform mobile implementations but cannot be shared.

The exception is Option C (TWA): the Android app is raxx-next. Every feature shipped to the web app is simultaneously available on Android. This is the genuine, non-trivial advantage of the wrapper approach — for a solo founder with a large web feature surface, it eliminates the "web feature, then mobile feature" duplication.


Maintenance Burden (Solo Founder)

Ranked from lightest to heaviest ongoing maintenance:

  1. Option C (TWA, Android only): 2 active codebases (web + iOS). Android improvements are subsumed into web work. Lowest friction.
  2. Option B-RN: 2 active codebases (web + React Native). But bridge risk on auth + billing paths.
  3. Option A: 3 codebases. However, the mobile apps are thin consumption surfaces — the structural parallelism between iOS and Android (same API, same Raptor endpoints, same auth flow) makes the duplication mechanical rather than complex. Once Android v1 exists, maintaining feature parity is modest overhead.
  4. Option B-KMP: 3 build targets with a shared Kotlin layer. Build complexity is the overhead; logic sharing is the benefit. At the current 20-file scale, the benefit is marginal.
  5. Option B-FL: Web + Flutter = 2 codebases, but Dart is a fourth language (TypeScript/Python/Swift/Dart). Context-switching cost is underestimated by the headline "2 codebases."

Recommendation

Option A — keep SwiftUI iOS, add native Kotlin/Jetpack Compose Android — deferred to post-launch.

Why not B-RN or B-FL

Both require throwing away PasskeyManager.swift and StoreKitManager.swift — the two most load-bearing, framework-specific integrations in the iOS app — in exchange for community bridges. ADR-0116 already made this call for iOS. The TestFlight Wave 2 investment is real. Resetting it carries regression risk on auth and billing, which are the paths where a financial app least tolerates surprise.

Why not B-KMP

KMP preserves iOS views and native auth/billing on both platforms. The objection is timing and marginal value: at 20 Swift files, the shared-logic benefit is modest, and Gradle + Xcode dual-build toolchain complexity is non-trivial to set up correctly (particularly with Swift package interop). KMP is worth revisiting when the codebase is larger and the team is more than one.

Why not Option C (TWA) as the long-term answer

A Trusted Web Activity on Android gives Android users "the website in Chrome." For most apps this is a reasonable trade. For Raxx — a financial app whose brand is structure, discipline, and trust — delivering a native experience on iOS and a web-wrapper experience on Android creates a quality asymmetry that contradicts the brand. Haptic feedback, native navigation gestures, APNs-quality push notifications, and a Keychain-equivalent credential store (Android Keystore) are available in a native app and not in a TWA. The first Android users are likely to compare.

If Option C is used: it should be explicitly scoped as a bridge strategy with a documented upgrade path to native Android, not a permanent answer.

Why defer

Pre-revenue, with iOS in TestFlight Wave 2, the marginal return on Android investment is lower than shipping iOS to production and validating revenue. US iOS market share (~55%) covers the early-adopter demographic for a financial app. The 8–12 weeks of Android native work has a better expected return after the first revenue signal validates the iOS model.

The single decision the operator must make

Confirm the iOS architecture is permanent (Option A) or authorize a cross-platform rewrite (Option B).

If the operator needs Android fast

Option C (TWA, Android only) gets Android distribution in 4–6 weeks without touching the iOS codebase. This requires:

  1. PWA baseline in raxx-next (service worker + web manifest, Lighthouse ≥ 90).
  2. bubblewrap-generated APK from the manifest.
  3. Digital Goods API implementation for Play Billing (2–3 additional weeks).
  4. assetlinks.json hosted at raxx.app for passkey binding.

This is the fastest credible path. It is not the highest-quality path.


iOS Readiness Plan Coupling

This ADR directly gates the concurrent iOS readiness plan:


Any Android app using passkeys on raxx.app requires a Digital Asset Links file:

GET https://raxx.app/.well-known/assetlinks.json
Content-Type: application/json

[{
  "relation": ["delegate_permission/common.handle_all_urls",
               "delegate_permission/common.get_login_creds"],
  "target": {
    "namespace": "android_app",
    "package_name": "<tbd — open question #3>",
    "sha256_cert_fingerprints": ["<tbd — release signing key fingerprint>"]
  }
}]

This file coexists with the existing iOS AASA file at the same origin. It is a Raptor / hosting task (one static file), not an Android codebase task. It must be in place before any Android passkey registration is attempted, including in staging.


Schema Impact

No schema changes required for this ADR itself. The subscription_source column planned in ADR-0007 ("web" / "ios" / "android") is the only schema addition needed when Android billing is implemented. No migration until Android billing is built.


  1. Now — pre-Android: Ship iOS to App Store production. No Android work.
  2. Post-launch spike (~2 weeks): Android native feasibility spike. Proof-of-concept: Android Credential Manager passkey registration against staging + Google Play Billing sandbox purchase. This is sub-card #1.
  3. Android v1 build (~8–12 weeks): Feature-parity with iOS app (auth, dashboard, strategies, billing). No live-trading path until paper-first gate is confirmed on Android.
  4. Play Store beta: Mirror TestFlight model.
  5. GA: Requires Android live-trading paper-first gate confirmation.

Security / GDPR Checklist


Open Questions (require operator decision before any sub-cards can be claimed)

  1. THE core decision: Option A (native iOS + native Android, deferred)? Option C (TWA bridge, fast Android)? Option B (cross-platform unification)? Everything downstream — iOS readiness plan continuation, Android sub-cards, billing backend — depends on this answer.

  2. Android launch timing: Co-launch with iOS? First 90 days post-iOS launch? Later? If "at iOS launch," Option A is not achievable (insufficient time); the only candidates are Option C or a pre-decision to begin Android native immediately.

  3. Android package name: Must be chosen before the Digital Asset Links file can be committed. Suggested: app.raxx.android — operator confirms.

  4. Google Play Developer Account: Is one already open under MooseQuest LLC? If not, the 48-hour registration wait is on the Android critical path.

  5. Cross-billing upgrade path (carry-over from ADR-0007): A user who subscribes on web (Stripe), then installs the Android app — what do they see? This interaction pattern must be decided before Android billing is implemented.


Revisit When