Skip to content

Native Mobile Standards

Two independent native apps per product: Kotlin + Compose on Android, Swift + SwiftUI on iOS.

No cross-platform UI framework — no Flutter, no React Native, no MAUI. Escape hatch: a client funding three or more platforms with heavy shared business rules can justify Kotlin Multiplatform for the domain layer only, UI native on both. That needs an ADR and CTO sign-off.

Concern Android iOS
Language Kotlin Swift, Swift 6 language mode
UI Jetpack Compose SwiftUI
State ViewModel + StateFlow<UiState> @Observable model
Async Coroutines + Flow async / await
DI Hilt Constructor injection from a composition root — no container
HTTP Retrofit + OkHttp + kotlinx.serialization URLSession + Codable
Dependencies Version catalog (libs.versions.toml), Kotlin DSL SPM, exact pins

iOS 17 is the hard floor@Observable and the current concurrency model require it.

  • Unidirectional data flow. The view renders one immutable UI state and emits events. The ViewModel or model owns state.
  • The repository seam is mandatory. All I/O goes through a repository — a view or ViewModel that touches Retrofit or URLSession directly fails review.
  • On a legacy app, the standard applies to new code. New screens are Compose / SwiftUI inside the existing app. You do not rewrite a working screen to hit a standard; you migrate it when it is being changed substantially anyway. A big-bang rewrite needs an ADR.
Platform Gate Where it lives
Android detekt with the formatting ruleset (it embeds the ktlint rules — one tool, one config) .editorconfig, config/detekt/detekt.yml
Android Kotlin compiler warnings are errors build.gradle.kts
iOS SwiftLint --strict — a warning is an error .swiftlint.yml
iOS Swift 6 language mode, strict concurrency the Xcode project / Package.swift
  • Every mobile repo has a PR-triggered pipeline that runs lint and unit tests. A repo without a green PR pipeline is not a delivered repo.
  • abortOnError false on Android lint is banned — it turns the gate off silently.
  • No suppression without a comment saying why.
  • If a formatter is ever added on iOS it is swift-format. One formatter, never two.

Signing material is a secret. Keystores, .p12 certificates, provisioning profiles, and store API keys live in Azure DevOps Secure Files; their passwords in a Key Vault–backed variable group. Never in the repo, never in build.gradle, never in pipeline YAML. If you find one committed, follow the secrets rule.

  • Debug builds use the local debug key. Only CI holds the release key.
  • Release builds ship with R8 enabled, and upload the mapping file / dSYM to the crash reporter.
  • Version is major.minor.patch, identical on both platforms for a release. versionCode / CFBundleVersion is the pipeline build counter — monotonic, never derived from the marketing string.
  • The minimum supported version is enforced by the API, not the store. The backend refuses clients below a configured floor and returns an upgrade response. Store rollout is not something we control; this is.
  • Floors: iOS supports the current release and the two before it. Android minSdk comes from Play install-base data with a floor of API 28, reviewed annually. targetSdk and the iOS SDK are always the latest — Google Play enforces a rolling annual targetSdk deadline.
  • Crash reporting is wired before the first client build. Push is FCM + APNs; add Huawei HMS only when the target market has real Huawei share, because it is a second provider to maintain.

Arabic is a first-class language, not a translation pass. No hard-coded user-visible strings — Android res/values*/strings.xml, iOS .strings through generated accessors.

  • Layout uses start / end, never left / right.
  • RTL is set once at the app root, never per screen — a per-view opt-in guarantees a screen will miss it and render LTR in Arabic.
  • Normalise Arabic-Indic digits to Latin before sending to the API.
  • Every screen is reviewed in Arabic before it is called done.

Android: JUnit + Turbine for ViewModel and repository logic, androidx.compose.ui.test for screen tests. iOS: Swift Testing for logic, XCUITest for the smoke suite. See Testing strategy for the coverage gate and the CI lanes.

See also: Examples · Testing · Pipelines & Environments.

👤 Owner: Fadel Sabbagh🗓 Last reviewed: 2026-07-25

اقرأ هذه الصفحة بالعربية