Services / Build
Mobile apps that survive real devices and real reviewers
We build iOS and Android applications in React Native, Flutter, Swift and Kotlin, with offline-first data architecture and a documented conflict policy, APNs and FCM push, deep linking that resolves correctly on cold start, staged rollout with crash gates, and store submission handled by us. The choice of framework is made against your requirements, in writing, with the cost of each named.
- Framework decision documented, not defaulted
- Offline conflict policy chosen per entity, not left to chance
- Performance budgets enforced in CI, not measured after launch
- Store submission, review responses and phased rollout run by us
At a glance
- Cross-platform
- React Native (New Architecture), Flutter 3.x
- Native
- Swift with SwiftUI and UIKit, Kotlin with Compose
- Local storage
- SQLite, WatermelonDB, Realm, Room, SwiftData
- Push and messaging
- APNs, FCM, Amazon SNS, OneSignal
- Crash and performance
- Sentry, Firebase Crashlytics, OpenTelemetry
- OS support policy
- Current major release plus two prior
- Availability target
- 99.95% on the supporting backend
- Enquiries
- nitesh@redcubical.com
Decision
React Native, Flutter or native Swift and Kotlin?
Framework selection
Pick React Native when you already have React engineers and want to share validation, models and API clients with the web. Pick Flutter when you want identical rendering on both platforms and a predictable UI layer. Pick native when the app is built around device capability, background execution, or platform surfaces such as widgets, watches and CarPlay.
| Factor | React Native | Flutter | Native Swift / Kotlin |
|---|---|---|---|
| Where it genuinely wins | Teams with an existing React or TypeScript codebase. Shared domain logic, validation and API clients across web and mobile | Design-led products needing pixel-identical UI on both platforms, complex custom rendering, or heavy animation | Camera, audio, Bluetooth LE, background location, HealthKit, ARKit, widgets, watch and TV apps, tight latency budgets |
| Honest cost | Two runtimes to reason about. Native module quality varies. The New Architecture migration is real work. Upgrades across a large dependency tree are the recurring tax | Dart is a second language for most teams, so hiring is narrower. Larger binary, roughly 4 to 8 MB before your code. Platform-idiomatic behaviour must be rebuilt rather than inherited | Two codebases, two review cycles, two sets of tests. Feature parity drifts unless deliberately managed. Highest ongoing cost |
| UI fidelity | Real platform components, so it feels native by default. Divergence between platforms must be handled in code | Own rendering engine. Identical everywhere, which is a benefit and a cost — it will not inherit new OS design language automatically | Perfect by definition. New OS design language arrives with the SDK |
| Team you need | React engineers plus one person genuinely comfortable in Xcode and Android Studio | Dart engineers plus platform-channel capability for native integrations | Separate iOS and Android engineers, or one very stretched generalist |
| Cost versus two native apps | Roughly 60 to 70 percent to build, 70 to 80 percent over three years once framework upgrades are counted | Roughly 60 to 70 percent to build, 70 to 80 percent over three years | Baseline, 100 percent. Predictable, if expensive |
| We recommend it when | Feature breadth matters more than device depth and the web team is React | Brand-controlled UI matters and the team will commit to Dart | The app is a device app rather than a screens app, or one platform dominates your revenue |
A hybrid is often the correct answer and rarely proposed: cross-platform for the ninety percent of screens that are lists, forms and detail views, with a native module for the one capability that needs it. We also see teams choose cross-platform then write so much platform-specific code that they carry the cost of both. If your feature list is dominated by device capability, name that early.
Architecture
Offline-first, and what happens when two devices disagree
Mobile networks fail in ways desktop networks do not: lifts, tunnels, aircraft, warehouse basements, patchy rural coverage. Offline-first is not a caching feature, it is a data model decision.
What offline-first means
The local database is the source of truth for the UI. Writes are queued with an idempotency key and a version or vector clock, then a background sync engine reconciles with the server. The design work is choosing a conflict resolution policy per entity: last-write-wins, CRDT, operational transform, or server-authoritative. Choose deliberately and write it down.
| Strategy | How it resolves | Data loss risk | Implementation cost | Right for |
|---|---|---|---|---|
| Last-write-wins (timestamp or version) | The write with the highest timestamp or version number replaces the other outright | Real and silent. The losing edit disappears with no user signal unless you add one | Low. Days, not weeks | Single-user-per-record data: personal settings, a user profile, a private draft, a reading position |
| CRDT (conflict-free replicated data types) | Data structures that converge mathematically regardless of merge order. Yjs and Automerge are the practical libraries | None by construction, though the converged result may not be what a human intended | High. New storage format, larger payloads, tombstone growth to manage | Genuinely collaborative content: shared documents, lists, boards, annotation, whiteboards |
| Operational transform | Operations are transformed against concurrent operations before being applied, preserving intent | None, if the transform functions are correct | Very high. Correctness proofs are hard and central coordination is usually required | Rich-text collaborative editing where character-level intent matters. Rarely justified outside that |
| Server-authoritative | The client proposes, the server validates against current state and may reject. The client reconciles to whatever the server says | None, but the user can lose work they typed offline if the server rejects | Low to moderate, and the easiest to reason about and audit | Money, stock, bookings, clinical records, anything with an invariant that must never be violated |
Most applications need more than one of these. A field service app we would typically build uses server-authoritative for job assignment and stock movements, field-level merge for job notes, and last-write-wins for user preferences. A single global strategy is usually a sign the question was not asked.
What the sync engine must do
- Durable outbound queue. Mutations persist in local storage before the UI acknowledges them, so an app kill mid-write loses nothing.
- Idempotency keys. A client-generated UUID per mutation, so a retry after an ambiguous timeout cannot create a duplicate order.
- Delta pull with a cursor. Pull changes since a server-issued cursor rather than refetching collections. Include soft-delete tombstones or deletions will never propagate.
- Bounded retry. Exponential backoff with jitter, a dead-letter state after a defined number of attempts, and a visible way for the user to see and retry a stuck item.
- Schema migration on device. Local databases outlive app versions. Every release needs a tested forward migration and a defined fallback if it fails.
The user experience of offline
- Never lie about state. A tick that means "saved on this device" must look different from one that means "confirmed by the server".
- Show the queue. Users tolerate delay far better than uncertainty. A pending-changes view removes most support tickets.
- Surface rejections as work, not errors. If the server rejected an offline edit, present it as an item to resolve with the original content preserved and editable.
- Test on real conditions. Airplane mode is the easy case. The hard case is a captive portal or 2 kB/s of throughput, which we simulate deliberately.
Engagement plumbing
Push notifications and deep linking that actually resolve
Push and deep linking
Push runs through APNs on iOS and FCM on Android, usually behind one server-side abstraction so your backend does not branch per platform. Deep linking uses Universal Links and Android App Links with verified domain association files, plus a deferred path so a link that triggered an install still lands on the right screen after first launch.
Push notifications: what goes wrong
- Token lifecycle. Tokens rotate on reinstall, restore and OS upgrade. Store them per device with a last-seen timestamp and prune aggressively, or your delivery metrics become meaningless.
- Permission timing. Asking for notification permission on first launch is the fastest way to a permanent no. Ask at the moment the value is obvious, after a pre-permission explainer you control.
- Silent pushes are a hint, not a guarantee. Content-available pushes on iOS are delivered at the system's discretion. Never build a data-consistency mechanism on them.
- Per-category preferences. One global on-off switch drives users to disable everything. Granular categories keep the transactional notifications working.
Deep linking: the cases teams miss
- Cold start. The link arrives before your navigation stack, session or feature flags exist. The router must be able to hold an intent and replay it after initialisation.
- Authentication gate. A link to a protected screen must survive a login round trip, including a redirect to the browser and back, and land on the intended screen rather than the home tab.
- Deferred deep linking. The user taps a link, has no app, installs from the store, and opens. Preserving the destination through that gap needs an attribution service or a matching heuristic, and it is never perfectly reliable.
- Domain association. The apple-app-site-association file and Android assetlinks.json must be served over HTTPS, without redirects, with the correct content type. Verification failures degrade silently to the browser.
- Validation in CI. We test link resolution for cold start, warm start, background, authenticated and unauthenticated states as part of the pipeline, because this is exactly the code that rots quietly.
Release management, phased rollout and forced updates
You cannot roll back a shipped binary. Once a build reaches a device, it is on that device until the user updates. Every control therefore sits before release or outside the binary.
-
Pre-submission checklist and store metadata review
Privacy manifest, tracking disclosure, permission purpose strings, account deletion path, in-app purchase restore, demo credentials and screenshots verified against a written checklist before submission rather than after rejection.
-
Submit with the release paused
Approve the build for release but hold distribution. Approval and go-live become two decisions, so you are never forced to ship on the reviewer timetable.
-
Staged rollout at 1, 5, 20, 50 then 100 percent
Android staged rollout in Play Console, iOS phased release over seven days. Each step holds long enough to gather real crash and ANR data from real hardware.
-
Automated gates between stages
Progression is blocked if crash-free sessions drop below 99.5 percent, ANR rate exceeds 0.5 percent, a new crash signature appears above a volume threshold, or a key funnel conversion regresses beyond its tolerance.
-
Server-side flags and kill switches
New features ship dark and are enabled by cohort from the server. Any risky path has a remote kill switch, because disabling a feature in seconds beats shipping a hotfix in days.
-
Minimum supported version endpoint
The app checks a server-provided minimum version at launch. A soft prompt nudges, a hard block is reserved for security fixes and breaking API changes, with a defined notice period before it becomes mandatory.
Submission
Why apps get rejected, and how we avoid it
Store review outcomes
Most rejections are not judgement calls, they are checklist failures. The recurring causes are missing in-app account deletion, purchases routed outside the store, privacy declarations that do not match observed network traffic, reviewer-inaccessible content behind a login, and crashes on the reviewer device. We run a written pre-submission checklist covering each one.
| Rejection reason | Store | Why it happens | How we avoid it |
|---|---|---|---|
| No in-app account deletion | Apple and Google | The app allows account creation but only offers deletion by email or web form | A deletion flow inside the app that removes or anonymises data, with the retention exceptions stated in plain language on the confirmation screen |
| Payments routed outside in-app purchase | Apple and Google | Digital goods or subscriptions link to an external checkout, or the app mentions cheaper prices elsewhere in a prohibited way | Classify every purchasable item as digital or physical during design. Digital goods use store billing with working restore. External-link entitlements are applied for where they legitimately apply |
| Privacy manifest or data disclosure mismatch | Apple and Google | The declared data collection does not match what reviewers observe on the wire, often because an analytics or advertising SDK collects more than the team realised | We inventory every SDK and its network calls, capture traffic during QA, and generate the privacy manifest and Play data safety form from that evidence rather than from memory |
| Reviewer could not access the app | Apple mainly | Login-walled app with no demo account, expired credentials, SMS OTP the reviewer cannot receive, or geofencing that blocks the review region | A permanent, non-expiring demo account with representative data, a bypass code for OTP flows documented in review notes, and geofencing exemptions for reviewer traffic |
| Crash, hang or broken flow on the reviewer device | Apple and Google | Untested OS version, oldest supported hardware, low memory, or an empty-state path nobody exercised | A device matrix that includes the oldest supported hardware and a low-memory Android device, plus automated smoke tests over first-run, empty-state and permission-denied paths |
| Permission requested without justification | Apple and Google | Background location, contacts, photos or microphone requested at launch, or a purpose string that restates the permission name | Just-in-time permission requests, purpose strings that state the user benefit, and removal of any permission not tied to a shipped feature |
| Insufficient functionality or thin web wrapper | Apple mainly | The app is a WebView around an existing site with no platform integration | If the honest answer is that the product is a website, we say so before you commission an app. If a shell is right, it earns its place with offline support, push, biometrics and native navigation |
Rejections are recoverable. A rejection answered the same day with the fix and a clear explanation usually clears on the next review cycle. What costs weeks is a rejection nobody owns. Store communication is our responsibility during an engagement, and we keep a written record of every review interaction so the next submission starts from evidence.
Device and OS support policy
Support policy is a commercial decision with an engineering cost, so we make it explicit and revisit it every two quarters against your own analytics rather than a general market chart.
| Dimension | Default policy | Why | Review cadence |
|---|---|---|---|
| iOS versions | Current major plus the two before it | Typically covers above 95 percent of active iOS devices and keeps you within the SDK features Apple expects you to use | At each September OS release |
| Android API levels | Current API level down to roughly API 26 to 28 depending on your analytics | Android upgrade curves are slower and vary hugely by market. Dropping too aggressively cuts real users in India, Africa and Latin America | Every six months |
| Physical test devices | One current flagship and one oldest-supported device per platform, plus one low-memory Android handset | Crashes concentrate on constrained hardware. A test fleet of new flagships proves very little | Annually |
| Screen classes | Small phone, standard phone, large phone, tablet portrait and landscape, plus foldable open and closed if your analytics show them | Layout regressions are the most common visual defect and the cheapest to catch with snapshot tests | Per release |
Tablet support is the item most often assumed and least often budgeted. A phone app stretched to a tablet reads as unfinished. If tablets matter, they are a design and QA line item, not a checkbox.
Performance
Mobile performance budgets we enforce in CI
A budget that is only measured after launch is an observation. These are checked on every release build, and a regression beyond tolerance fails the pipeline.
How performance is governed
We set numeric budgets for cold start, frame rate, bundle and install size, memory, network payload and battery, measured on the oldest supported device rather than a flagship. Regressions block the release. The reason is simple: performance work deferred until after launch competes with features and always loses.
| Metric | Target | Fails the build at | Measured how | Usual cause of regression |
|---|---|---|---|---|
| Cold start to first meaningful frame | Under 1.5 s on current hardware, under 2.5 s on oldest supported | Above 3.0 s on oldest supported | Automated launch trace on physical devices, median of 20 runs | Synchronous work in application start-up: SDK initialisation, database migration, remote config fetch on the critical path |
| Scroll and animation frame rate | 60 fps sustained, 120 fps where the display supports it | More than 1 percent of frames exceeding the frame deadline in a scripted scroll | Instrumented scroll test on a long list with real images and real data volumes | Layout work per frame, unmemoised list items, image decoding on the UI thread, oversized images |
| JavaScript or Dart bundle size | Under 4 MB for a React Native bundle, under 6 MB for a Flutter release engine plus code | A 10 percent increase versus the previous release without written justification | Bundle analyser on every release build, tracked as a time series | A dependency pulled in for one helper, moment-style locale data, unreferenced assets, duplicated transitive versions |
| Battery drain in a 30-minute active session | Under 4 percent on a reference device | Above 8 percent, or any wake-lock held beyond its scripted window | Battery historian on Android and energy log on iOS, scripted journey | Polling instead of push, high-accuracy location left running, unbatched background sync, retry storms without backoff |
| Crash-free sessions | Above 99.7 percent | Below 99.5 percent halts staged rollout progression | Crashlytics or Sentry release health, per rollout stage | Untested OS version, unchecked optional value, background task exceeding its allotted time |
| ANR rate (Android) | Below 0.3 percent of sessions | Above 0.5 percent halts rollout | Play Console vitals per release | Disk or network I/O on the main thread, synchronous database work during start-up, lock contention |
Budgets are agreed with you before implementation and are visible on a dashboard, not buried in a pipeline log. Where a business requirement genuinely justifies exceeding a budget, we record the exception with an owner and a review date. What we do not do is quietly move the number.
Hardening
Mobile security hardening and accessibility
Mobile security posture
On device we use platform key storage, TLS with certificate or public-key pinning, biometric gating for sensitive actions, tamper and root detection, and obfuscation on release builds. All of it runs on hardware the attacker controls, so it raises cost rather than removing risk. Authorisation is always enforced server-side.
What we implement on the client
- Keychain and Keystore. Tokens and secrets in the iOS Keychain with the appropriate accessibility class, or the Android Keystore backed by hardware where available. Never in shared preferences, never in plain files, never in a JavaScript bundle.
- Short-lived tokens. Access tokens measured in minutes with rotating refresh tokens and server-side revocation. A stolen device should stop being useful quickly.
- Certificate or public-key pinning. Pin to an intermediate or a public key rather than a leaf certificate, keep a backup pin, and ship a remote configuration path so rotation does not require an emergency release. Pinning without a rotation plan is a self-inflicted outage waiting for an expiry date.
- Biometric gating. Face ID, Touch ID or BiometricPrompt in front of sensitive actions, with a passcode fallback and no assumption that biometric success authorises anything on its own.
- Root and jailbreak signals. Treated as risk signals feeding a server-side decision, not as a local gate. Escalate, step up authentication or limit functionality; do not simply exit.
- Obfuscation and anti-tamper. R8 and ProGuard on Android, symbol stripping on iOS, integrity checks on release builds. This slows reverse engineering; it does not prevent it.
- Attestation. App Attest and Play Integrity to raise confidence that a request came from a genuine, unmodified app, verified server-side and never trusted from the client.
Accessibility on mobile
- Screen reader labels. Every interactive element has an accessible label, role and state for VoiceOver and TalkBack. Icon-only buttons are the most frequent failure we find.
- Focus and reading order. Logical traversal order, modals that trap focus and return it on dismissal, and announcements for state changes rather than silent updates.
- Dynamic Type and font scale. Layouts tested at the largest text size, with no fixed-height containers around scalable text and no truncation of essential content.
- Touch target size. Minimum 44 by 44 points on iOS and 48 by 48 density-independent pixels on Android, with adequate spacing between adjacent targets.
- Contrast. At least 4.5:1 for body text and 3:1 for large text and meaningful graphics, verified in both light and dark themes.
- Reduced motion. Honour the system setting. Parallax and large transitions cause genuine nausea for some users.
- Testing. Automated checks in CI for labels, contrast and target size, plus manual passes with VoiceOver and TalkBack. Automation catches roughly a third of real accessibility failures, so the manual pass is not optional.
Honest answer
When a responsive web app or PWA is the better decision
Web or app
If your users visit occasionally rather than habitually, arrive from search or shared links, and your value is content or a transactional flow, a fast responsive web application will serve them better than an app they must find, install and update. Install friction is real: most people who intend to install an app never finish.
| Consideration | Native or cross-platform app | PWA | Responsive web app |
|---|---|---|---|
| Distribution | Store listing, review process, install step. Discoverable in store search | A URL, installable to the home screen. No store gatekeeper | A URL. Zero friction, fully indexable |
| Push notifications | Full support with rich content and background handling | Supported on Android, and on iOS 16.4 and later only for a home-screen-installed PWA | Not available |
| Device capability | Camera, Bluetooth LE, background location, biometrics, health data, NFC, widgets, watch | Camera and geolocation while in use. Limited and inconsistent beyond that, especially on iOS | Basic capture and geolocation with permission |
| Search visibility | Store search only. App content is largely invisible to web search | Fully indexable | Fully indexable |
| Right when | Repeat daily use, device capability at the core, offline field work, notification-driven workflows | Frequent-but-not-daily use, mostly online, Android-heavy audience, notifications useful but not critical | Occasional use, content or transactional flows, search-led acquisition, tight budget |
The honest limits of a PWA on iOS matter: push requires the user to add the app to the home screen, background sync is not available, and storage can be evicted after periods of disuse. If your iOS users need reliable notifications, a PWA is not a substitute for an app. Anyone claiming full parity has not shipped one to an iPhone-heavy audience.
The sequence we most often recommend
For a new product with an unproven audience, ship a fast responsive web application first, instrument it properly, and learn which journeys people repeat. Then build the app around those journeys with device capability and notifications as the reason it exists. This costs less in total than commissioning an app for a feature set nobody has validated, and it means the app you eventually build is designed around evidence rather than assumption.
Where this does not apply: field operations with genuine connectivity gaps, anything with a hardware or Bluetooth dependency, and products where a competitor's app has already set the expectation. In those cases the app is the product and web is the fallback. We will give you our reading of which situation you are in during scoping, before there is a proposal to defend.
Answers
Mobile app development questions
React Native, Flutter or native — which should we choose?
If your app is mostly forms, lists, feeds and API calls, cross-platform wins on cost and consistency: React Native when you already have a React web team and want to share domain logic, Flutter when pixel-identical UI and predictable rendering matter more than JavaScript reuse. Go native when the app is built around camera, audio, Bluetooth, background location, HealthKit, ARKit, widgets or watch surfaces, or when latency budgets are tight enough that a bridge is a liability.
Is cross-platform genuinely cheaper?
For the initial build, yes: typically 30 to 40 percent less than two native codebases, not the 50 percent the marketing implies, because platform-specific work never disappears entirely. Over three years the gap narrows further, because you carry the framework upgrade tax on top of the two platform SDK upgrade cycles. Cross-platform saves real money on breadth of features and saves nothing on depth.
What does offline-first actually mean in practice?
The app reads and writes to a local database as the source of truth for the UI, and a background sync engine reconciles with the server. Every write carries an idempotency key and a version or vector clock. You must choose and document a conflict policy per entity type. The hard part is not caching, it is deciding what happens when two devices edited the same record.
How long does an App Store review take, and what gets apps rejected?
Apple review is usually 24 to 48 hours, occasionally a week for first submissions or apps touching regulated categories. The recurring rejection causes we see are missing or non-restorable in-app purchases, account deletion not offered in-app, privacy manifest and tracking disclosures that do not match observed network behaviour, login-walled demo content without working credentials, and crashes on the reviewer device. Almost all of it is preventable with a pre-submission checklist.
How do you release safely on mobile when you cannot roll back?
You cannot recall a shipped binary, so the controls sit before and around release: staged rollout at 1, 5, 20, 50 then 100 percent with crash-free-session and ANR gates at each step, server-side feature flags so a bad feature can be disabled without a new build, remote kill switches on risky paths, and a minimum-supported-version endpoint for the rare forced update.
Which OS versions and devices do you support?
Our default policy is the current major OS release plus the two before it, which typically covers above 95 percent of the installed base on iOS and above 90 percent on Android. We validate on a device matrix that includes the oldest supported hardware you actually see in analytics, not just recent flagships, plus one low-memory Android device because that is where crashes concentrate.
Can you make a mobile app impossible to tamper with?
No, and neither can anyone else. Certificate pinning, keystore and Keychain storage, root and jailbreak detection, and code obfuscation raise the effort required and stop opportunistic attacks. All of them run on hardware the attacker controls, so a determined adversary with the device can defeat them. Client-side controls are defence in depth. Authorisation must be enforced server-side.
When is a native app the wrong answer?
When the value is content or transactional web flows, when your users arrive from search or shared links rather than repeat sessions, or when installation friction would cost you more users than a native shell would win. In those cases a fast responsive web app, optionally a PWA with installability and push on both platforms, ships sooner and costs less to maintain. We say so before quoting an app.
Start with a two-week mobile scoping sprint
Fixed fee. You get a framework recommendation with the reasoning written down, an offline and conflict-resolution design, a performance budget, a store submission checklist against your specific category, and a costed delivery plan. The output is yours whether or not you engage us to build it.