---
title: "Mobile applications, technically — Thoughtful Robots"
description: "How Thoughtful Robots engineers mobile applications and SDKs: native, React Native and hybrid WebView strategy; architecture; lifecycle; device capabilities; offline sync; media; testing; accessibility; and operations."
source: "https://thoughtfulrobots.ai/mobile-applications-tech"
---

# Native is a behaviour, not a framework.

Mobile engineering is choosing what must belong to the device, what can be shared, and what has to keep working through interruption. We use Swift and Kotlin, React Native, or a governed WebView shell according to the product — then engineer lifecycle, permissions, offline state, device capabilities, accessibility and store delivery as first-class contracts.

## What the app has to survive.

These are mobile responsibilities before they are framework choices. The tools show where our work has landed; each linked section explains the engineering contract behind them.

| Layer | What we use |
| --- | --- |
| [Platform & application shape](#platform-application-shape) | Swift / SwiftUI Kotlin / Compose React Native / Expo Hybrid WebView |
| [Application architecture](#application-architecture) | Unidirectional state MVVM Kotlin Multiplatform Platform adapters |
| [Native SDK & module engineering](#native-sdk-modules) | Kotlin / Swift React Native modules CocoaPods Gradle Typed events |
| [Navigation & lifecycle](#navigation-lifecycle) | Native stacks Expo Router Deep links State restoration |
| [Device capabilities](#device-capabilities) | BLE Camera Location Motion Push Background services |
| [Offline data & sync](#offline-data-sync) | SQLite / SQLDelight Local cache WorkManager Conflict policy |
| [Hybrid WebView contracts](#hybrid-webview-contracts) | Typed bridge Origin policy Health watchdogs Native handoff |
| [Media & performance](#media-performance) | Native playback Resumable upload Image pipelines Performance budgets |
| [Testing & accessibility](#testing-accessibility) | Unit tests Sensor simulation UI automation Device QA VoiceOver / TalkBack |
| [Release & operations](#release-operations) | EAS Build Fastlane SDK distribution Store rollout Diagnostics |

## Choose what must be native, *not how much code can be shared.*

The first mobile decision is where the product needs platform fidelity, device access and independent release control. We choose Swift and SwiftUI, Kotlin and Jetpack Compose, React Native with Expo, or an explicit hybrid shell from those requirements. Code reuse matters, but it follows the interaction, hardware, lifecycle, performance and team constraints rather than overruling them.

- The product depends deeply on Bluetooth, sensors, media, background execution or platform frameworks Swift Kotlin

- Platform-specific interaction and performance are central to the experience SwiftUI Jetpack Compose

- iOS and Android need independent capability or release roadmaps Xcode Gradle

- A shared product surface is valuable but navigation and device behaviour must remain native React Native Expo

- The required device capabilities have maintained modules or a deliberately owned native module Expo modules Native modules

- One team should ship both platforms without pretending platform differences do not exist TypeScript Platform files

- The primary product already lives on the web and needs a focused native host WKWebView Android WebView

- Native capabilities can be expressed as a small, versioned bridge contract Typed messages Capability adapters

- The shell owns navigation, permissions, recovery and external handoff—not merely a URL Native shell Web runtime

We can work inside other serious mobile stacks. These are current choices, not the edge of our capability.

## Share the rules, *keep the platform seams visible.*

A mobile codebase has to remain understandable through view recreation, process death, intermittent connectivity and years of SDK changes. We separate presentation state, domain rules, data access and platform capabilities, then decide which layer can be shared. Native UI can sit over common Kotlin domain and persistence code; React Native can isolate Expo and native APIs behind typed adapters; a hybrid shell can keep its bridge contract independent of its screens.

- One observable state model drives each screen and makes loading, empty, error and recovered states explicit Unidirectional state MVVM

- Domain behaviour stays independent of view controllers, composables and React components Use cases State reducers

- Network, storage, clock and device services enter through replaceable interfaces Repositories Platform adapters

- Cross-platform logic is shared only where both platforms have the same contract Kotlin Multiplatform TypeScript

- Design tokens can be shared while touch targets, typography and controls respect each platform Tokens Native components

- White-label, environment and product variants share modules without sharing mutable configuration Expo config Gradle flavors Xcode schemes

A shared codebase should reduce duplicate decisions. It should not hide platform behaviour behind conditionals spread through the UI.

## Make the native boundary *a product of its own.*

A cross-platform SDK is a compatibility surface across several runtimes: the underlying iOS and Android frameworks, the Swift and Kotlin adapters, the React Native bridge, the JavaScript or TypeScript API, and every consumer build. We design that surface deliberately, preserve platform differences where they matter, and release it with the documentation, sample applications, testing hooks and version evidence expected of a maintained product.

- Typed configuration, results, errors and discriminated events map consistently across platforms TypeScript Kotlin Swift

- Asynchronous native operations resolve explicit success and failure instead of leaking bridge timing Promises Operation results

- Events that arrive without a foreground UI are persisted before being exposed to product state Event emitter Durable handoff

- Platform-only capabilities remain marked and testable rather than forced into false parity Capability query Platform API

- Native dependencies integrate through standard package surfaces CocoaPods Gradle NPM

- A compatibility matrix for native SDK, React Native, Xcode, CocoaPods and Android build versions

- Semantic release notes that identify additive, deprecated and breaking behaviour

- Minimal iOS and Android sample applications for integration and regression checks

- Generated API documentation and migration guides built from the published types

- Test-only native hooks that cannot be enabled accidentally in production

A native module is not finished when it compiles in the example app. It is finished when a consumer can integrate, upgrade, diagnose and test it without reading its internals.

## Treat interruption *as a normal state.*

Mobile sessions are repeatedly paused, resumed, recreated and entered from outside the app. Navigation therefore has to be a restorable product state rather than a stack of incidental screen calls. We map universal links, app links, push payloads and internal routes through one parser, preserve safe state, and make foreground, background and terminated launches converge on the intended destination.

- Native navigation stacks with platform-correct back behaviour and gestures Navigation Hardware back

- A single validated route model for links, notifications and in-app movement Universal links App links

- External destinations handed to the system browser, phone or mail app by policy URL policy Native handoff

- Authentication and onboarding redirects preserve the intended post-login destination Route intent Auth gates

- Foregrounding refreshes time-sensitive state without discarding in-progress work

- Backgrounding persists only the minimum safe restoration state

- Process death and WebView eviction have an explicit recovery path

- Duplicate notification and link deliveries are idempotent

- Version migrations run before restored data is trusted

A cold launch from a notification and a warm in-app navigation may share a route, but they do not share the same lifecycle.

## Make hardware access *a product contract.*

Bluetooth, camera, location, motion, notifications, biometrics and background work are not library checkboxes. Each capability has permissions, unavailable states, operating-system limits and privacy consequences. We isolate it behind a narrow interface, request access in context, explain the value before the system prompt, and design the useful path when access is denied or hardware disappears.

- BLE discovery, connection, GATT services and packet handling kept behind a device state machine Core Bluetooth Android BLE

- Camera, image selection and media capture with bounded file and privacy handling AVFoundation CameraX Expo ImagePicker

- Location, motion and geofencing modes selected from the actual foreground or background need Core Location Fused Location Activity Recognition

- Push registration separated from notification routing and user preference state APNs FCM Expo Notifications

- Credentials and sensitive local material kept in platform-protected storage Keychain Keystore Biometrics

- Background work scheduled within power, network and operating-system constraints BGTaskScheduler WorkManager

- Continuous sensing recovers after process loss, device restart and application replacement Foreground service Boot receiver Background session

- Battery, permission and sensor restrictions become actionable settings health Diagnostics Settings handoff

- The request follows a user action and a plain-language rationale

- Denied, restricted and permanently denied states have distinct UI

- The application remains useful when the capability is absent

- Collection and retention are no broader than the feature requires

- Always-on access has an explicit consent, pause, deletion and support path

The operating system owns the final permission decision. The product owns whether that decision is understandable and recoverable.

## Design the product *for the missing network.*

Offline is not a banner shown after a failed request. It is a decision about which data is authoritative, which actions can be queued, how freshness is communicated, and what happens when two devices change the same record. We keep a bounded local model, record pending operations durably, make retries safe, and reconcile against the server without silently discarding user intent.

- Structured product state persisted with explicit schema and migrations SQLite SQLDelight

- Media and derived caches separated from user-authored or authoritative state File cache Eviction policy

- Sensitive records encrypted or deliberately excluded from local persistence Encrypted storage Retention

- Queued mutations have stable identifiers and can be retried safely

- Freshness and pending state are visible to the user

- Conflict behaviour is chosen per domain, not hidden behind last-write-wins

- Background sync respects power, metered network and OS scheduling limits

- Local schema and cached data survive application upgrades

A cache accelerates a read. An offline model preserves a user workflow. They require different guarantees.

## Make the bridge *smaller than the app.*

A serious hybrid application is a native host with a governed web runtime—not a full-screen browser. We define the bridge as a versioned API, keep native capabilities narrow, permit only trusted origins, and supervise both document loading and web-app readiness. Navigation, external URLs, process termination, slow starts and bridge incompatibility all receive explicit policy and recovery UI.

- Typed request, response and event schemas shared by native and web consumers Bridge contract Runtime validation

- First-party origin allowlists and separate policy for frames, external links and development Origin policy Navigation guard

- Document-load, hydration-readiness and liveness signals monitored independently Watchdogs Health probes

- Hardware back, deep links and push destinations translated into product routes Route bridge History contract

- Media, browser, telephone and other specialised flows handed to native surfaces Native playback System browser

- Content-process eviction and renderer failure recover without trapping the user Reload policy Bounded retry

- Old native shells can tolerate additive web changes

- Unsupported bridge versions fail visibly before a destructive action

- Every native method validates origin, payload and permission state

- A browser-based test bed can exercise the web half without a device

A WebView reduces duplicated interface code only if the bridge, lifecycle and failure states are engineered as first-class mobile behaviour.

## Budget the device, *not only the response time.*

Mobile performance includes launch time, frame stability, memory pressure, battery, radio use and package size. We measure on representative physical devices, keep image and list work away from the critical interaction path, stream large media, and move playback or capture into native components when the web or JavaScript runtime cannot meet lifecycle and resource requirements.

- Cold and warm launch paths measured through the first usable interaction Launch tracing Readiness markers

- Long lists virtualised and recomposition or re-render boundaries kept narrow Lazy lists Memoisation

- Images resized, cached and decoded for the rendered target rather than the source asset Image pipeline Responsive assets

- Video streamed and handed to platform playback where backgrounding or controls require it AVPlayer ExoPlayer Expo Video

- Large uploads resumable, observable and decoupled from one foreground session Multipart upload Progress state

- CPU, network and sensor polling constrained to protect battery and thermals Profiling Backoff

A smooth simulator is not a performance result. Low-memory devices, slow storage, poor networks and background transitions are part of the benchmark.

## Test the contracts *across real device states.*

The fastest mobile tests hold domain rules, route parsing, bridge policies and sync decisions outside the UI. Integration tests then prove storage, permissions, native modules and network adapters; a smaller UI suite protects critical journeys on supported OS versions and devices. Accessibility is part of those contracts, including semantics, focus order, dynamic type, contrast, reduced motion and touch targets.

- Pure state transitions, policies and parsers run without a simulator Kotlin test XCTest Node test

- Storage, background workers, BLE and network adapters tested at their platform boundary Instrumentation Ktor mock

- Location, motion, trip and collision scenarios simulated deterministically—including while backgrounded Native test SDK Preset simulation

- Compose, SwiftUI and React Native components checked in meaningful states Compose UI XCUITest React Native tests

- Critical launch, login, deep-link and offline journeys run on a maintained device matrix Device labs E2E

- Hybrid web content receives browser tests and a native contract suite Test bed Bridge tests

- Every control exposes a name, role, value and useful focus order

- Dynamic type and font scaling do not hide actions or content

- VoiceOver and TalkBack complete the primary workflows

- Animations respect reduced-motion preferences

- Touch targets, contrast and error messages meet platform guidance

A screenshot-perfect UI can still be unusable with a screen reader, large text, one hand, a hardware keyboard or an interrupted network.

## Treat the store build *as an operated system.*

Mobile delivery combines source, native dependencies, environment configuration, signing identity, store metadata and server compatibility. We automate reproducible builds, separate product and environment variants, distribute betas early, stage production rollout and connect crashes and performance regressions to the exact release. Because rollback is constrained by store review, compatibility and remote controls are designed before launch.

- Deterministic iOS and Android builds from reviewed configuration EAS Build Xcode Gradle

- Signing credentials and provisioning isolated from source and scoped to release automation App Store Connect Play signing

- Native libraries and cross-platform modules published with compatible artifacts and types CocoaPods Gradle NPM Semantic versioning

- Beta lanes publish to internal testers before store submission TestFlight Firebase Distribution Fastlane

- Staged rollout, feature flags and server compatibility reduce blast radius Phased release Remote config

- Crashes, hangs, launch failures and performance tied to version and device context Crash reporting Release health

- Support can collect bounded on-device logs and state without exposing unrestricted user data Rolling logs Diagnostic bundle

- Minimum-version and data-migration policy handles clients that cannot update immediately Version gates Backward compatibility

- Automated tests and supported-device smoke tests are green

- Privacy declarations, permissions and store metadata match runtime behaviour

- The backend remains compatible with the previous supported app version

- Crash-free sessions, launch health and critical conversion are within thresholds

- Feature disablement and incident ownership are known

On mobile, “roll back” often means waiting for another reviewed binary. Safe releases depend on compatibility, staged exposure and remote containment.

## We have built at every mobile seam.

We have engineered the mobile seams that fail outside the happy path: BLE and sensor state, background location, durable local queues, typed native bridges, deep-link routing and media handoff. Those systems recover after process death, restart and app upgrades, expose permission and battery restrictions, and reproduce device events for testing.

## An AI-native team, with a factory behind it.

Team Foundry is our software factory — what the team uses day in, day out to deliver projects. It accelerates the work and validates it: every change arrives with the checks it passed, the session that produced it, and a person accountable for it.
