Skip to main content
All services

Mobile Applications

No hotfix.No assumptions.

Modern mobile products with careful attention to performance and interaction, backed by infrastructure that scales with the install base.

  • React Native
  • Offline-first
  • Push
  • App Store
  • DEVICE · GAP · SERVICE
  • SIX TIERS
  • THE GAP IS THE DESIGN

Mobile Applications.

  1. 01BuildOnce, for both platforms
  2. 02ShipThrough someone else's review
  3. 03SyncAcross a network you don't control
  4. 04SupportEvery version still installed

Mobile is the only place we work where you cannot fix it this afternoon. The thing you ship is a binary, it goes through someone else's review, and once it is installed you have no way to reach it until the user decides to update — which many of them never will. Every version you have ever released is still out there, still calling your API, still expecting the response it was compiled against.

It is also the only place where the network is a feature rather than an assumption. A lift, a tunnel, a rural road and a hotel wifi that resolves DNS but routes nothing are all normal operating conditions. An app that treats those as errors is an app that is broken most of the time for some of its users.

So the engineering sits in two places most web work never has to think about: what the app can still do when it cannot reach you, and how a change actually gets to a person holding a phone. Local state that survives a cold start, a sync layer that queues and reconciles rather than failing, APIs that stay kind to versions you would rather forget, and a release path where a bad build can be stopped before it reaches everyone.

Capabilities

What we actually build.

Engagements are usually a subset of this list, scoped to where the app is. This is engineering work on your product — not a template, not a wrapper around your website, and not a rebuild we have decided you need.

06 capabilities · 30 focus areas

01Cross-Platform Applications
One React Native codebase serving both platforms, written so the shared 90% stays shared and the parts that genuinely differ — navigation feel, permission dialogs, share sheets — are allowed to differ rather than being flattened into something that feels foreign on both.
  • React Native
  • Shared architecture
  • Platform conventions
  • Navigation
  • Accessibility
02Native Device Integration
The parts of a phone that make an app worth installing: camera and photo library, location, biometrics, secure storage, background tasks and local notifications — including the permission flows around them, which is where most of the real work and nearly all of the rejections live.
  • Camera and media
  • Location
  • Biometrics
  • Background tasks
  • Permissions
03Offline & Sync
Apps that keep working on a train. A local store the UI reads from, writes that queue and drain when a connection returns, operations safe to replay, and a conflict rule decided up front — so reconnecting is a background event rather than a decision the user has to make.
  • Local-first reads
  • Write queue
  • Conflict resolution
  • Delta sync
  • Idempotency
04Mobile Backends
APIs built knowing the client cannot be updated on demand: contracts that stay compatible with versions still installed, payloads shaped for a metered connection, token refresh that survives a month in the background, and push handled as a hint rather than a guarantee.
  • Version-tolerant APIs
  • Payload shaping
  • Push delivery
  • Token refresh
  • Rate limits
05Release Engineering
The path from a merged branch to a phone: reproducible builds, signing and credentials that are not one person's laptop, store submission, staged rollout, a remote kill switch for a feature that has to stop today, and a forced-upgrade floor for versions that can no longer be supported.
  • Build and signing
  • Store submission
  • Staged rollout
  • Kill switch
  • Forced upgrade
06Performance & Reliability
Cold start measured rather than assumed, lists that stay smooth on the cheap Android device your users actually own, memory and battery behaviour treated as budgets, and crash reporting broken down by version and OS so a regression is attributable instead of anecdotal.
  • Cold start
  • List performance
  • Memory and battery
  • Crash reporting
  • Per-version telemetry

Where a responsive web app would genuinely serve you better, we will say so. An app that exists only to wrap a website costs a review cycle for every change and earns nothing that a browser could not already do.

System architecture

Everything hard happens at the gap.

Six tiers, split by a discontinuity that no other system we build has to design around. Four of them ship inside a binary and cannot be corrected without another release; two can be fixed this afternoon. Knowing which is which is most of what mobile architecture is.

04 release to fix · 02 deploy to fix

06 tiers · 30 controls

  1. 01

    Device

    The environment you do not control

    Release to fix

    OS versions you cannot drop, screen sizes and safe areas you did not choose, permissions the user may refuse and later revoke, a battery budget, and an operating system that will suspend you mid-task without asking. This tier is a set of constraints rather than code — but every decision above it is made against them.

    • OS version floor
    • Screen and safe areas
    • Permission flows
    • Background limits
    • Battery and thermal
  2. 02

    App

    The surface, and its lifecycle

    Release to fixFrontend05

    One React Native codebase across both platforms, and the lifecycle web work never has: cold start, resume from suspension, deep links into arbitrary screens, and state restored so a user killed by the OS mid-form does not come back to an empty one. Accessibility and gesture behaviour are platform contracts here, not preferences.

    • Navigation and deep links
    • Cold start and resume
    • State restoration
    • Platform conventions
    • Accessibility
  3. 03

    State

    What the app holds on its own

    Release to fixDatabase04

    The local store is the reason an app opens to content instead of a spinner, which makes it real persisted data with real migrations — on a device you cannot log into, belonging to a user who may be four versions behind. Schema changes here have to run forward on a phone, unattended, without losing anything.

    • Local store
    • On-device migrations
    • Cache policy
    • Secure storage
    • Data at rest
  4. 04

    Sync

    Reconciling across the gap

    Release to fix

    The tier that exists only because the network cannot be assumed. Writes queue locally and drain when a connection returns, every operation is safe to send twice, and conflicting edits resolve by a rule decided in advance rather than by whoever reconnects last. Offline is a state to be designed, not an error to be reported.

    • Write queue
    • Retry and backoff
    • Idempotent operations
    • Conflict resolution
    • Delta sync
    The network cannot be assumed
  5. 05

    Service

    What the app talks to

    Deploy to fixBackend05

    APIs designed knowing the client is old. Responses stay compatible with versions still installed, payloads are shaped for a metered connection rather than a datacentre link, and push is treated as a delivery hint rather than a guarantee — because a notification that never arrives is an ordinary Tuesday.

    • Version-tolerant contracts
    • Payload shaping
    • Push delivery
    • Auth and token refresh
    • Rate limits
  6. 06

    Release

    How a change reaches a person

    Deploy to fixInfrastructure04

    Build, sign, submit, wait. Review is a queue you do not control, so the release path is built to survive it: staged rollout so a bad build reaches a fraction rather than everyone, a remote kill for a feature that has to stop today, a forced-upgrade floor for versions that can no longer be supported, and crash reporting broken down by version so you know which of them is on fire.

    • Build and signing
    • Store submission
    • Staged rollout
    • Forced upgrade floor
    • Crash and version telemetry
Stage 01
Device00:01
Stage 02
The gap00:06
Stage 03
Reconcile00:07
Source
1920 × 108030fps · 1.27MB

Still frame

Device · App · State · Sync · Service · Release

Delivery lifecycle

How the work is sequenced.

The order is shaped by one fact: the later a mobile mistake is found, the more expensive it gets, because the fix has to travel through review and then wait for people to update. Two of these stages exist purely to move discovery earlier.

  1. 01

    Frame

    Decide what works without a connection.

    Which screens must function offline, what the app is allowed to do while it cannot reach you, and which OS versions and devices are in scope. These answers shape the local store and the sync design, and they are far cheaper to decide now than to retrofit later.

  2. 02

    Model

    Design the local store and the contract together.

    What lives on the device, what the server owns, how the two reconcile, and what happens when they disagree. Designed alongside the API rather than after it, because a sync layer bolted onto a contract that assumed connectivity is the most expensive rewrite on this list.

  3. 03

    Build

    On real devices, from the first week.

    Vertical slices running on physical hardware — including a slow Android device — rather than only on a simulator on a fast laptop. Permission flows, deep links and lifecycle transitions exercised as features, because they are features, and they are where the platform pushes back.

  4. 04

    Harden

    Test the conditions nobody demos.

    Airplane mode mid-write, a token expired during suspension, a permission revoked in Settings, a cold start straight into a deep link, low storage, and the OS killing the app between two screens. Plus accessibility and the store's own review guidelines, checked before submission rather than after a rejection.

  5. 05

    Release

    Ship gradually, and watch by version.

    Staged rollout so a regression reaches a fraction of the install base, crash and performance telemetry broken down by app version and OS, and a decision already made about when a version stops being supported. Then the part nobody plans for: keeping the old ones working.

Built for the install base

What makes it survivable.

An app is not finished when it ships, because what shipped is now permanent. These are the properties that decide whether the versions already in the wild are an asset or a liability, and they are the ones we would ask about if we were buying this work.

01Offline as a designed state
The app opens to content, accepts input and queues work without a connection. Reconnecting drains the queue in the background rather than presenting the user with a decision they have no context to make.
02APIs kind to old clients
Responses stay compatible with versions still installed, additive changes are preferred to breaking ones, and when a version genuinely has to be retired there is a forced-upgrade path rather than a silent failure.
03On-device migrations that run unattended
Local schema changes execute on a phone with no operator, possibly skipping several versions at once, without losing queued writes. There is no rollback on a device, so forward-only and tested is the only safe shape.
04A release that can be stopped
Staged rollout, a remote kill switch for a feature, and a documented decision about the supported version floor. Review means a fix is days away at best, so the ability to halt a bad build matters more than the ability to patch one.
05Telemetry broken down by version
Crashes, performance and errors attributed to app version, OS version and device class. Without that split, a regression affecting one build looks like background noise across the whole install base.
06Signing that is not one laptop
Certificates, provisioning and store credentials held as shared, recoverable infrastructure. The alternative is a single point of failure that blocks every release the moment that person is unavailable.

What we build

Shapes of app this work suits.

These are app shapes rather than case studies — the situations where the mobile-specific constraints are the deciding factor. Our published project work lives under Selected Work; nothing below is presented as a delivered engagement.

01Work that happens where the signal does not
Inspections, deliveries, agriculture, logistics, site surveys. The app is the system of record for hours at a time and reconciles later, which makes the sync layer the product rather than a detail of it.
02A companion to a platform you already run
Where the web product exists and mobile has to be a first-class client rather than a viewport — real sessions, push, background refresh, and an API that stops assuming a browser on a good connection.
03A consumer product people open daily
Where cold start, list smoothness and battery behaviour are the product experience, and a regression on a mid-range Android device is a retention problem rather than a performance ticket.
04An app that depends on the hardware
Camera, scanning, location, biometrics or background capture, where the permission flows and the platform's own rules shape the design as much as the feature does.
05Two native codebases that have diverged
Where iOS and Android have drifted into different products and every feature is built twice. Consolidating onto one React Native codebase, usually incrementally, screen by screen.
06An app that has become frightening to release
Where nobody wants to press submit. The work is the release path itself — reproducible builds, recoverable signing, staged rollout and per-version telemetry — so shipping stops being an event.

Systems layer

What it is built on.

Read from the same engineering stack the rest of the site publishes. Mobile appears there as React Native and nothing else, which is the honest description of how we build: one cross-platform codebase, not separate native teams.

01Frontend05React Native is the mobile surface — one codebase, both platforms
  • React
  • Next.js
  • TypeScript
  • Tailwind CSS
  • React Native
02Backend05The APIs an app talks to, which have to stay kind to old versions
  • Node.js
  • Python
  • REST APIs
  • GraphQL
  • WebSockets
03Database04State on the server, and the local store the app keeps beside it
  • PostgreSQL
  • Redis
  • Supabase
  • Vector Databases
04Integrations07Push, payments and identity — the parts the platform owners mediate
  • Stripe
  • Paystack
  • Flutterwave
  • Third-party APIs
  • Webhooks
  • OAuth
  • Message Queues
05Cloud04Where the service runs, and what the app reaches over a bad connection
  • AWS
  • Vercel
  • Cloudflare
  • Object Storage
06Infrastructure04Building, signing and shipping a binary, and watching it once it is out
  • Docker
  • CI/CD
  • Observability
  • Infrastructure as Code

Building something that hasto work on a train?

The most useful first conversation is about what the app must still do with no connection, and which versions you are prepared to keep supporting. Both are cheap to decide now and expensive once there is an install base.