Skip to content
Starone

Capability

Mobile applications engineered for real devices, networks and release constraints

A mobile application is a long-lived client that the team cannot redeploy at will. It runs on intermittent networks, is killed by the operating system without notice and stays installed in old versions for months. We design the application, its API and its release pipeline around those facts.

Overview

Mobile engineering differs from web engineering in three structural ways. The network is intermittent, so the application must be useful without it and reconcile when it returns. The operating system controls the process lifecycle, suspending or terminating the application to protect memory and battery. Releases pass through store review and then depend on users updating, so several versions are always in use and a defect cannot be withdrawn by a redeploy.

Those constraints shape the whole system, not only the client. The API must stay compatible with versions shipped months ago, payloads must suit metered connections, and authentication must survive token expiry while the application was in the background. On the device, credentials belong in hardware-backed storage, local data needs a schema and migrations like any database, and start-up time, frame rate, memory and energy use are measured properties.

We build with Swift and SwiftUI on iOS, Kotlin and Jetpack Compose on Android, and Flutter or React Native where a shared codebase is the better trade. The choice between them is made on product requirements and team structure, and the backend contract, offline model and release process are designed alongside the first screens.

What we engineer

Technical areas

01

Native and cross-platform development

Native development gives immediate access to every platform API, the best start-up and rendering characteristics and platform-standard behaviour, at the cost of two codebases. Flutter renders its own widgets through its own engine and gives high visual consistency across platforms; React Native drives native views from JavaScript or TypeScript and shares skills with a web team. Both still require native code for some integrations.

  • Swift, SwiftUI and UIKit
  • Kotlin, Jetpack Compose and coroutines
  • Flutter and Dart
  • React Native with the New Architecture and Hermes
  • Kotlin Multiplatform for shared business logic
  • Native modules and platform channels
02

Offline-first data and synchronisation

In an offline-first design the interface reads and writes a local database, and a sync engine reconciles with the server in the background. Local writes are queued as mutations with idempotency keys and replayed in order when connectivity returns. Conflict handling is defined per entity, because the right rule for a note is not the right rule for a payment.

  • SQLite with Room, GRDB, Drift or WatermelonDB
  • Mutation outbox with idempotency keys
  • Delta sync with cursors or change tokens
  • Last-write-wins, field-level merge and CRDTs
  • Server-authoritative rules for financial state
  • Background sync with WorkManager and BGTaskScheduler
03

Security, storage and authentication

Secrets are stored in the iOS Keychain and the Android Keystore, with keys generated in the Secure Enclave or a StrongBox-backed element where the device supports it. Biometric authentication gates the use of a hardware-held key, so a successful match releases a cryptographic operation instead of returning a boolean that a tampered application could bypass. Sign-in uses OAuth 2.0 authorisation code flow with PKCE through the system browser.

  • Keychain and Android Keystore
  • Face ID, Touch ID and BiometricPrompt with CryptoObject
  • OAuth 2.0 with PKCE and refresh token rotation
  • Certificate pinning with a rotation plan
  • App Attest and Play Integrity
  • Jailbreak, root and tamper signals
  • Screen capture and backup exclusions for sensitive data
04

Mobile API architecture, push and deep links

A backend-for-frontend shapes responses to screens, reducing round trips on high-latency networks. Push notifications through APNs and FCM are best-effort, so they are treated as a signal to fetch state and never as the carrier of state. Universal Links and Android App Links bind URLs to the application through files hosted on a verified domain, which prevents another application from claiming them.

  • Backend-for-frontend per client type
  • Cursor pagination and sparse field selection
  • Compression, ETags and conditional requests
  • APNs and Firebase Cloud Messaging
  • Universal Links and Android App Links
  • Minimum supported version and forced upgrade
  • Remote configuration and feature flags
05

Performance, analytics and crash reporting

Performance work targets cold start, frame pacing, memory and energy. A frame must be produced in about 16.7 milliseconds at 60 Hz and 8.3 milliseconds at 120 Hz, so work is kept off the main thread and lists are virtualised. Crash and hang reports are symbolicated with uploaded dSYM and R8 mapping files and tracked per release as crash-free session rates.

  • Cold start and time-to-first-frame profiling
  • Xcode Instruments and Android Studio Profiler
  • Baseline Profiles and R8 optimisation
  • Image caching and downsampling
  • Request batching to limit radio wake-ups
  • Firebase Crashlytics and Sentry
  • Privacy-aware product analytics
06

CI/CD and store delivery

Mobile pipelines build, sign, test and distribute reproducibly from clean machines, with signing identities held in a secrets store and never on a laptop. Builds flow through internal testing, TestFlight and Play testing tracks before a phased release on the App Store or a staged rollout on Google Play. Rollout is halted automatically or manually when crash or ANR rates regress.

  • Fastlane, Xcode Cloud, GitHub Actions and Bitrise
  • Code signing, provisioning profiles and Play App Signing
  • Unit, snapshot and device-farm UI tests
  • TestFlight and Play internal, closed and open tracks
  • Phased release and staged rollout
  • Store metadata, privacy declarations and review notes

How mobile applications connect to cloud-native backends

The mobile client reaches the backend through an edge layer: a CDN and web application firewall, then an API gateway that terminates TLS, validates tokens, applies rate limits per device and user, and routes requests. Behind the gateway, a backend-for-frontend aggregates calls to domain services running on Kubernetes or a serverless platform and returns one response shaped for a screen. This keeps chatty orchestration inside the data centre, where a call costs a millisecond, and off the mobile network, where it costs hundreds.

Identity flows through OpenID Connect. The application opens the system browser, completes authorisation code flow with PKCE and receives a short-lived access token and a rotating refresh token, which it stores in the Keychain or Keystore. The gateway verifies the token signature and passes verified claims downstream; services authorise each request against those claims. Attestation from App Attest or Play Integrity can be added as a signal that the caller is a genuine, unmodified build, which is useful for high-risk operations and never a substitute for server-side authorisation.

State changes that originate on the server reach the device by three routes. Push notifications through APNs and FCM wake the application or inform the user and prompt a fetch. A WebSocket or Server-Sent Events connection carries live updates while the application is in the foreground, and is closed in the background to respect operating system limits. Delta sync on launch and resume closes any gap, so correctness never depends on a push arriving. On the server, these are fed by the same event stream that drives other consumers, with a notification service fanning events out to device tokens.

  • Version compatibility: the application sends its version and platform on every request; the API stays additive and the backend can require an upgrade below a minimum supported version
  • Resilience: client-side timeouts, retries with backoff and idempotency keys make mutations safe to replay after connectivity loss
  • Efficiency: compressed, paginated responses, conditional requests and image variants sized for the device reduce data and battery use
  • Media: uploads go directly to object storage through pre-signed, resumable URLs instead of passing through application servers
  • Observability: a trace identifier generated on the device is propagated through the gateway and services so that a slow screen can be followed end to end

Architecture

Mobile application backend
  1. Mobile Apps

    • iOS (Swift)
    • Android (Kotlin)
    • Flutter
    • Offline store
  2. Edge

    • CDN
    • API gateway
    • Authentication
    • Rate limiting
  3. BFF / Mobile API

    One round trip per screen where possible

    • Screen-shaped responses
    • API versioning
    • Sync endpoints
    • Feature flags
  4. Services

    • Accounts
    • Content
    • Payments
    • Media processing
  5. Data & Notifications

    • PostgreSQL
    • Redis
    • Object storage
    • APNs and FCM push
  6. Observability

    • Crash reporting
    • API tracing
    • Release health
    • Client analytics
Mobile clients talk to a backend-for-frontend that shapes responses for each screen and shields the apps from service changes. Old app versions stay in the field, so API versioning and release health are part of the architecture.

Offline-first architecture and sync conflicts

Offline support is a spectrum. Read caching shows the last known data without a connection and is cheap. Queued writes let users act offline and require an ordered outbox, idempotent server endpoints and a visible pending state. Full offline-first, with the local database as the source of truth for the interface and bidirectional sync, is the most capable and the most complex, and should be chosen when users work without coverage for long periods, as in field service, logistics and inspection.

Conflicts arise when the same record changes on two devices or on device and server between syncs. Last-write-wins by server timestamp is simple and silently discards one edit, which is acceptable for preferences and not for shared documents. Field-level merge preserves non-overlapping edits. CRDTs converge automatically for collaborative text and lists at the cost of metadata growth and a more difficult data model. For balances, stock and bookings, the server validates each queued operation against current state and may reject it, and the application must present that rejection clearly.

Sync also needs handling for deletions through tombstones, for schema migrations of data that has not yet been uploaded, and for partial failure midway through a batch. Device clocks are unreliable, so ordering uses server-assigned versions or logical clocks, never local wall-clock time.

Native or cross-platform: how to choose

The decision rests on how much of the product is platform-specific. Applications dominated by forms, lists and API-driven content share well, and a single Flutter or React Native codebase removes most duplicated feature work and keeps the two platforms in step. Applications built around the camera pipeline, Bluetooth peripherals, background location, on-device machine learning, widgets, watch companions or the newest operating system features spend much of their effort at the native boundary, where a cross-platform layer adds indirection without saving work.

Team structure matters as much as technology. A web team fluent in TypeScript and React is productive in React Native quickly, while Flutter requires adopting Dart and gives tighter control of rendering in return. Native teams own the full toolchain and are not dependent on a framework's release schedule when a new operating system version ships. Kotlin Multiplatform is a middle path: shared networking, persistence and domain logic, with fully native interfaces.

Whatever the choice, some native expertise is required. Signing, push configuration, store submission, deep link association, performance profiling and many third-party SDKs are platform-specific, and a cross-platform team without that knowledge stalls at exactly those points.

How we engineer it

Our approach

We design the client, the API and the release process together, because a decision in one constrains the other two for as long as old versions remain installed.

  1. 01

    Define platform scope and constraints

    We establish target operating system versions, device classes, offline requirements, regulatory and store policy constraints, and the device capabilities the product depends on. These determine the native or cross-platform decision.

  2. 02

    Design the API contract and sync model

    Endpoints, pagination, error semantics, idempotency and the compatibility policy for old versions are specified with the backend team. Where offline writes are needed, the conflict rule for each entity is decided before any storage code is written.

  3. 03

    Establish architecture and pipeline early

    A unidirectional data flow such as MVVM or MVI, dependency injection, a modular project structure and the local persistence layer are set up alongside a signed CI build that reaches testers from the first week of development.

  4. 04

    Build security into the foundations

    Authentication flow, secure storage, biometric gating, transport security and attestation are implemented as shared infrastructure and reviewed against the OWASP MASVS before feature work depends on them.

  5. 05

    Test on real devices and real networks

    Unit and snapshot tests run on every commit, and UI tests run on a device matrix covering older hardware and operating system versions. Network conditioning exercises loss, latency and mid-request disconnection, and process-death tests verify state restoration.

  6. 06

    Release in stages and watch the data

    Builds move through internal and beta tracks to a phased production rollout, with crash-free rate, ANR rate, start-up time and key funnel events compared against the previous version before exposure is widened.

Trade-offs

Decisions that shape the design

The questions we work through with your team before committing to an architecture.

Native, Flutter or React Native?
Choose native when the product depends on deep platform integration, demanding graphics or immediate adoption of new operating system features, and when you can staff both platforms. Choose Flutter for a consistent custom interface across platforms from one team; choose React Native when you have TypeScript and React expertise and want to share code and people with the web. Avoid choosing cross-platform solely to halve cost, since platform-specific work does not disappear.
How much offline capability does the product need?
Use read caching by default: it is cheap and removes most blank screens. Add queued writes when users must complete actions in poor coverage and the operations are naturally idempotent. Commit to full offline-first with bidirectional sync only when extended offline work is a core use case, because conflict resolution, migrations of unsynced data and support complexity are ongoing costs.
Should the mobile application have its own backend-for-frontend?
Yes when the screens need data from several services, when mobile and web diverge in what they display, or when the general API returns far more than the device needs. The BFF also gives one place to handle old application versions. Skip it for a small product with one API and one client, where it would be an extra deployable with nothing to aggregate.
Should we use certificate pinning?
Pin for applications handling payments, health data or credentials where interception on a compromised network or device is in the threat model. Pin to the public key of an intermediate or to a set including backup keys, and have a tested rotation path, because an expired pin with no fallback locks every installed copy out of the backend. For lower-risk applications, platform trust stores with Certificate Transparency are a reasonable baseline.
How do we support old application versions still in use?
Keep the API additive, have clients ignore unknown fields and enum values, and gate new behaviour with server-driven flags. Publish a minimum supported version from the backend so that the application can prompt for or require an update, and retire old API behaviour only after analytics show usage below an agreed threshold. Plan this from the first release; it cannot be retrofitted into versions already shipped.
Can we ship fixes without waiting for store review?
Server-driven configuration and feature flags can disable or alter behaviour instantly on both platforms. React Native applications can also deliver JavaScript bundle updates over the air within store policies, which cover fixes and minor changes but not changes to the application's primary purpose. Native code changes always require a new binary and review, so risky features should ship behind a remote kill switch.

Production readiness

What makes it production-ready

How it is secured

Tokens and keys are held in the Keychain and Keystore with hardware backing and biometric or device-credential access control, and nothing sensitive is written to logs, plain preferences, backups or screenshots of the task switcher. Transport uses TLS with pinning where the threat model requires it, and release builds are obfuscated and checked for debug flags. The client is treated as untrusted: every rule that matters is enforced again on the server.

How it scales

Scale on mobile means a growing device population across many versions, so the backend carries the load through a CDN, gateway rate limits per device and paginated, cacheable responses. Push fan-out is handled by a queue-backed notification service that batches sends and prunes invalid tokens. The codebase scales through feature modules with explicit interfaces, which keeps build times and merge conflicts under control as the team grows.

How it is monitored

Crash, hang and ANR reports are symbolicated and grouped per release, with breadcrumbs and device context attached. Performance monitoring records cold start, slow and frozen frames and network request latency from real devices, and trace identifiers link a client request to its backend spans. Analytics events follow a versioned schema and respect consent and platform tracking rules.

How it is deployed

The pipeline produces signed, versioned builds from tagged commits, uploads symbols and submits to TestFlight and Play testing tracks automatically. Production uses App Store phased release and Google Play staged rollout, both of which can be paused. Review time is not guaranteed, so release calendars allow for it and urgent mitigation relies on remote flags.

How it integrates

Applications integrate with identity providers through OpenID Connect, with payment, mapping, messaging and analytics SDKs behind internal interfaces so that each can be replaced or stubbed in tests. Each third-party SDK is assessed for size, start-up cost, permissions and data collection, since its behaviour must be declared in App Store privacy labels and the Play Data safety form.

Technologies

Chosen to fit the workload and your existing estate.

  • Swift and SwiftUI
  • Kotlin and Jetpack Compose
  • Flutter
  • React Native
  • Kotlin Multiplatform
  • SQLite
  • Firebase Cloud Messaging
  • Apple Push Notification service
  • Firebase Crashlytics
  • Sentry
  • Fastlane
  • GitHub Actions
  • TestFlight
  • Google Play Console

From zero to production

The engineering lifecycle

  1. 01

    Idea

  2. 02

    Discovery

  3. 03

    System architecture

  4. 04

    UX / product design

  5. 05

    Application engineering

  6. 06

    AI / data layer

  7. 07

    Cloud infrastructure

  8. 08

    Security

  9. 09

    QA & performance

  10. 10

    Deployment

  11. 11

    Observability

  12. 12

    Continuous evolution

FAQ

Common questions

Should we build native or cross-platform?

It depends on how much of the product is platform-specific and on the team you have. Content and workflow applications share well across Flutter or React Native, while products built on camera, Bluetooth, background processing or new operating system features are usually better served by Swift and Kotlin. A short technical spike on the riskiest device integration settles the question more reliably than a comparison table.

How do push notifications actually work?

The application registers with APNs on iOS or FCM on Android and receives a device token, which it sends to your backend. The backend asks the platform service to deliver a message to that token, and the platform delivers it when the device is reachable. Delivery is best-effort and may be delayed, collapsed or dropped, and tokens change, so the backend must handle invalid tokens and the application must fetch authoritative state from the API.

How long does store review take, and what causes rejection?

Review time varies and is not guaranteed by either store, so releases should not be scheduled on the assumption of a fixed turnaround. Common causes of rejection include incomplete privacy declarations, missing account deletion, permission requests without a clear purpose, broken sign-in for the reviewer and payment flows that conflict with in-app purchase rules. Supplying test credentials and review notes with each submission avoids many of them.

How is sensitive data protected on the device?

Credentials and keys go in the Keychain or Keystore with hardware-backed protection, and larger sensitive datasets are encrypted with a key held there. Biometric checks are bound to a cryptographic key so they cannot be bypassed by patching a conditional. Sensitive screens are excluded from screenshots and backups, and the server never relies on the client having enforced a rule.

Can one backend serve both web and mobile clients?

Yes. The domain services and data are shared, and a thin backend-for-frontend per client type adapts them: the mobile one favours fewer, smaller, version-tolerant responses, while the web one can change in step with the web deployment. This keeps business rules in one place and lets each client evolve at its own release cadence.

Have a system that needs to work in production?

Tell us what you are building. An engineer will read it and reply within two working days.