Mobile Apps

Mobile App Development

Most businesses do not need a mobile app. A responsive web application covers the majority of use cases, avoids app store review cycles, and eliminates platform-specific maintenance. We say this as a company that builds custom mobile app development projects regularly, because the distinction matters.

The short version: If your users need offline data capture, push notifications, background processing, or native hardware access (NFC, BLE, biometrics), build a mobile app. If you need forms, dashboards, and content display with reliable connectivity, a web app is the better choice at lower cost.

A mobile app earns its place when the use case demands capabilities that browsers cannot provide: offline data capture in areas with no signal, push notifications that reach users outside the browser, background GPS tracking for location-aware workflows, camera and barcode scanning with native performance, or biometric authentication tied to the device keychain. If your requirements include none of these, a custom web application will serve you better at lower cost.

There is a middle ground. Progressive Web Apps (PWAs) can cache static assets, work offline for read-only content, and provide home-screen shortcuts without app store distribution. For simple use cases (a staff directory, a reference manual, a read-only dashboard), a PWA may be enough. Where PWAs fall short is complex offline writes, background sync beyond what a service worker can manage, and reliable push notification delivery on iOS (though iOS 16.4+ added Web Push support for home-screen PWAs, narrowing this gap for simpler use cases). When the use case crosses that line, a native app built with React Native is the correct move.

Our mobile work follows the same principle that guides everything else we build: the technology serves the business constraint, not the other way around.


When a Mobile App Is the Right Call

The decision between mobile app and web app is a technical one, not a preference. Three categories of requirement push a project toward native.

Hardware access beyond the browser sandbox

NFC for asset tagging. Bluetooth Low Energy for connecting to field equipment. Camera access with real-time processing rather than file upload. Accelerometer and gyroscope for equipment monitoring. The Web Bluetooth and Web NFC APIs exist but remain incomplete, inconsistent across devices, and absent on iOS Safari for most use cases.

Offline-first data capture

Field service engineers, delivery drivers, warehouse operatives. These users spend significant parts of their day without reliable connectivity. A web app with a service worker can cache static assets, but it cannot reliably queue complex transactional writes, resolve merge conflicts when the device reconnects, or handle large binary attachments like photos and signatures.

Persistent background processes

Geofencing that triggers when a driver enters a delivery zone (the mobile counterpart to the location-aware features we build on the web). Background sync that uploads queued work orders while the phone sits in a pocket. Push notifications that wake the app to refresh critical data. These require native platform capabilities that Progressive Web Apps approximate but do not match.

If your use case fits none of these categories, start with a responsive web app. You can always add a mobile client later, and an API-first architecture makes that straightforward.


Why Expo and React Native, Not Native Swift or Kotlin

For business applications, cross platform app development with React Native (via Expo) is the right default. Not because it is cheaper per se, but because the constraints of business apps align with what cross-platform frameworks handle well.

Business applications are form-heavy, data-driven, and workflow-oriented. They display lists, capture input, show status, and navigate between screens. They are not GPU-intensive games or camera-filter apps that demand frame-level control over the rendering pipeline. For this category of work, React Native produces genuinely native UI components (not WebView wrappers) with a single TypeScript codebase that targets both iOS and Android.

The naive approach

Build from scratch using the bare React Native CLI. Manage native iOS and Android project files directly, handle Xcode and Android Studio configuration, write custom native modules for each platform, and maintain two separate build pipelines. For a team whose primary expertise is web and API development, this creates a maintenance surface that compounds over time.

The robust pattern

Use Expo's managed workflow. Expo abstracts native project configuration behind a declarative app.json, provides pre-built native modules for camera, file system, notifications, biometrics, and secure storage, and handles the build pipeline via EAS Build. When you need custom native code, config plugins and development builds give you escape hatches without ejecting.

We pair Expo with a Laravel API backend. The mobile app is a client, identical in architectural role to a web frontend. Authentication tokens, business logic, validation rules, and data persistence all live server-side. The mobile app handles presentation, offline caching, and device-specific features. This separation means the mobile codebase stays thin, focused, and replaceable.

What about Flutter?

Flutter is a credible cross-platform framework with strong backing from Google, and its custom rendering engine (Skia/Impeller) produces smooth UIs. For teams already working in Dart, or for apps that demand pixel-perfect custom UI across platforms, Flutter is a reasonable choice. We use React Native with Expo because our stack is TypeScript and React end to end: the web dashboards, the API clients, and the mobile apps all share the same language, the same mental model, and (where appropriate) the same utility libraries. For a team whose backend is Laravel and whose web frontends are React, adding Flutter means introducing a second language and a second UI paradigm. That is not a dealbreaker, but it is a cost that needs justifying.

When native development makes sense

Native Swift or Kotlin is the right choice when the app's core value depends on platform-specific capabilities that React Native cannot reach: AR experiences using ARKit or ARCore at full fidelity, custom camera pipelines with real-time ML inference, or deep Siri/Google Assistant integrations. In four years of mobile work for business clients, we have not yet needed to go fully native. The Expo ecosystem covers business app requirements well.

Capability quick reference

This table covers the most common business app requirements and where each approach fits.

CapabilityExpo / React NativeNative Swift / Kotlin
Forms, lists, maps, dashboards Fully supported Fully supported
Offline data + sync expo-sqlite, custom sync Core Data / Room
Push notifications expo-notifications (APNs + FCM) Native APIs
Biometric auth expo-local-authentication Native APIs
Camera + barcode scanning expo-camera Native APIs
AR (ARKit / ARCore) Limited, third-party bridges Full fidelity
Real-time ML inference Requires custom native modules Core ML / ML Kit
Deep OS integration (Siri, Widgets) Partial, via config plugins Full access

API-First Architecture: The Mobile App as a Client

Every mobile project we build follows an API-first pattern. The Laravel application exposes a RESTful API (or, where appropriate, GraphQL) that serves as the single source of truth. The mobile app consumes this API exactly as a web frontend would.

  • Shared business logic Validation rules, pricing calculations, workflow state transitions, and permission checks execute server-side. When a business rule changes, it changes in one place.
  • Independent deployment The API evolves behind versioned endpoints. A mobile app update and a backend change do not need to ship simultaneously. App store review cycles introduce delays that server deployments do not.
  • Multiple clients from one API The same Laravel API serves the mobile app, the web dashboard, webhook integrations, and background job processors. Adding a mobile client does not require rebuilding the backend.

The pattern is straightforward: Laravel handles authentication (Sanctum for token-based auth), authorisation (policies and gates), business logic (services and actions), and data (Eloquent with PostgreSQL). Expo handles screens, navigation, local state, offline queue, and device features. The data model is designed once, server-side, and the mobile app consumes it through well-defined API contracts.

This is fundamentally a build vs buy decision. If a SaaS product covers your mobile use case, use it. Custom mobile development makes sense when the workflow is yours, the data is yours, and the off-the-shelf options require too many compromises.


Offline-First Patterns for Field Workers

Offline support is the most common reason our clients need a mobile app rather than a web app. Field service engineers inspecting equipment. Delivery drivers confirming drop-offs. Warehouse operatives scanning stock in buildings with poor signal. These are the same people whose work feeds into your service delivery system, and they cannot wait for connectivity to do their jobs.

The naive approach: Check for network availability before each API call and show an error when offline. This fails because connectivity is not binary. A device can report "connected" while sitting on a network that drops 60% of packets.
The robust pattern: Assume the network is unreliable. Every write operation goes to a local queue (SQLite). The queue processor syncs when connectivity is available, using exponential backoff.

Each queued operation carries a timestamp, a unique client-generated ID, and enough context for the server to resolve conflicts. Conflict resolution follows a last-write-wins strategy for most business data, with explicit merge logic for cases where that would cause data loss.

Attachments (photos, signatures, PDF annotations) queue separately with chunked upload support. A 5 MB photo on a 2G connection will fail if sent as a single request. Chunked uploads with resume capability mean the file eventually arrives without requiring the user to retry.

This pattern requires server-side support. The Laravel API accepts idempotent writes keyed by client-generated UUIDs, handles out-of-order arrival gracefully, and returns sync status that the client uses to update its local database. The server-side queue processing follows the same patterns we use for background job infrastructure across all our applications.

Offline failure modes to test before shipping:

  • Duplicate submissions from retry storms when connectivity flickers
  • Stale writes overwriting newer data from another device
  • Expired auth tokens during background sync after extended offline periods
  • Attachment upload resume after mid-transfer connection drops
  • Storage quota exhaustion on older devices with limited internal memory
  • Queue ordering assumptions that break when operations arrive out of sequence

Push Notifications and Real-Time Updates

Push notifications in a business app serve a specific purpose: alerting users to events that require action. A new work order assigned. An approval waiting. A delivery window changed. They are not marketing tools.

Permission strategy

iOS requires explicit notification permission, and Apple recommends against requesting it on first launch without context. The pattern that works: wait until the user encounters a feature that benefits from notifications (for example, when they are first assigned a task), explain what they will receive, then trigger the permission prompt. In our experience, this approach typically achieves noticeably higher opt-in rates in business apps compared to prompt-on-launch, because users understand the value before being asked.

Technical architecture

Expo's notifications module handles device token registration for both APNs (iOS) and FCM (Android). On first launch, the app requests permission and registers the device's push token with the server. The Laravel backend stores these device tokens per user (a user may have multiple devices) and dispatches notifications through a queue worker using Laravel's built-in notification system with custom channels for push delivery.

Foreground notifications

Update the app's state while it is open. A new item appears in a list. A badge count increments. These use the app's WebSocket connection for live updates (the same real-time infrastructure we use for web dashboards) or a short-polling mechanism, not push notifications.

Background notifications

Arrive when the app is closed or backgrounded. They carry a payload the app uses to update its local data store on next launch, plus a visible alert for the user. Silent notifications can trigger background sync on both platforms, though delivery is on a best-effort basis: the OS may throttle or defer them, particularly when the device is in a low-power state.


Authentication and Security Patterns

Mobile apps handle authentication differently from web apps because the device itself becomes a factor. The patterns we use are designed around this constraint.

Token-based authentication

The user authenticates once with email and password (or SSO). Following the standard OAuth 2.0 refresh/access pattern, the server returns a long-lived refresh token and a short-lived access token. The refresh token is stored in the platform's secure storage (iOS Keychain, Android Keystore) via expo-secure-store, not in AsyncStorage. The access token lives in memory and expires after 15 minutes.

Biometric authentication

After initial login, the app offers Face ID or fingerprint unlock. A successful biometric check unlocks the securely stored refresh token, which exchanges for a new access token. The server never sees biometric data; it only sees a valid token.

Certificate pinning

For apps handling sensitive business data, we pin the expected TLS certificate in the app binary. This prevents man-in-the-middle attacks even on compromised networks. The trade-off is that certificate rotation requires an app update, so we plan pin rotations 30 days before certificate expiry.

Remote session management

The server tracks active device sessions per user. An administrator can revoke a specific device's access (for example, when a phone is lost) without affecting the user's other devices.

These patterns align with the security and operations standards we apply across all IGC projects.


App Store Deployment and OTA Updates

Getting a business app into production involves two distinct channels: app store releases and over-the-air (OTA) updates.

App store releases go through Apple's App Review and Google Play's review process. EAS Build compiles the native binary from the Expo project, signs it with the appropriate certificates, and submits it. Apple's review typically takes 24-48 hours; Google's is usually under 6 hours. For internal business apps, Apple's Enterprise programme or TestFlight and Google's internal testing tracks bypass public review.

OTA updates via EAS Update are the more interesting capability. Because Expo apps load JavaScript bundles at runtime, you can push code changes (UI fixes, business logic updates, new screens) without going through app store review. The app checks for updates on launch, downloads the new bundle in the background, and applies it on the next restart. A bug fix can reach every user's device within hours, not days.

The practical split: The majority of updates to a typical business app are JavaScript-only and eligible for OTA delivery. The remainder (native dependency updates, new device features, Expo SDK upgrades) go through the app store.

We maintain three channels: development (latest builds for internal testing), staging (release candidates for client review), and production (live builds). EAS Update channels map directly to these environments, each pointing at the corresponding Laravel API environment.

Ongoing maintenance obligations

A mobile app is not a one-time build. Ongoing annual maintenance covers concrete, scheduled work. Apple ships a major iOS release every September, and Google follows with Android in Q3-Q4. Each release can break existing behaviour in areas like background execution, notification permissions, and privacy controls. Expo SDK upgrades ship quarterly, and skipping versions compounds migration effort. App store policies change regularly, particularly around data collection declarations and minimum API level requirements. Provisioning profiles and signing certificates expire on fixed schedules.

This is the same principle that applies to all custom software maintenance: the build gets you through the door, the ongoing investment is what keeps it working. For mobile specifically, the cadence is faster and less forgiving than for web applications, because Apple and Google set the timeline, not you.


Performance Considerations for Business Apps

Mobile app performance for business use cases differs from consumer app optimisation. The priorities are: reliable data loading on variable connections, smooth list scrolling for large datasets, and minimal battery drain during background sync. Performance is a user experience concern first and a technical one second. A field worker waiting three seconds for a list to render while standing in a warehouse is three seconds of lost patience.

Render performance

React Native's FlatList handles large lists through virtualisation. Keep list items shallow (three to four levels of nesting maximum) and move heavy computation to useMemo hooks.

Network efficiency

Batch related requests, use cursor-based pagination, and cache responses locally so repeat views load from SQLite rather than the network.

Battery impact

Sync aggressively on WiFi and charging, conservatively on cellular (every 15 minutes), and defer non-critical sync when battery drops below 20%.


Testing Mobile Apps

Mobile testing is harder than web testing because the surface area is wider: two platforms, multiple OS versions, varying screen sizes, and connectivity states that cannot be fully simulated. We split testing into three layers.

Unit and component tests use React Native Testing Library to verify individual screens and components in isolation. These run fast, catch regressions early, and form the bulk of the test suite. Integration tests verify that the offline queue, sync engine, and API client work together correctly. These run against a local Laravel API and cover the failure modes that matter most: network timeouts, conflict resolution, and token refresh during sync. End-to-end tests use Detox to drive the app on real simulators, verifying complete user flows like login, data capture, offline queue drain, and push notification handling.

The investment in automated testing pays for itself within the first two Expo SDK upgrades. Without it, every upgrade is a manual regression test across two platforms. With it, confidence comes from a CI pipeline rather than a spreadsheet of manual checks.


When a Mobile App Is Not the Answer

Honesty matters more than a project fee. Many businesses approach us wanting a mobile app when a responsive web application would serve them better.

The app is primarily informational. A staff directory, a company handbook, a client-facing portfolio. These are websites. A responsive web app with a home screen shortcut provides the same experience.
All users have reliable connectivity. If your team works in offices with WiFi or urban areas with consistent 4G/5G, the offline-first argument disappears.
The budget does not cover ongoing maintenance. A mobile app is not a one-time build. OS updates, library deprecations, and app store policy changes require meaningful annual investment. If you are still working out what custom software costs in general, add mobile-specific overhead on top.
The timeline demands it. A web app can ship incrementally with no review process. A mobile app requires enough functionality to pass app store review on first submission.

When a web app is the right answer, we build it as a custom web application with the same Laravel backend. If the mobile requirement emerges later, the API-first architecture means adding a mobile client is additive, not a rebuild.


Discuss Your Mobile Project

If your team works in the field, handles offline data capture, or needs device-level features that browsers cannot provide, a mobile app may be the right fit. Mobile apps are delivered as part of a custom build, paired with the same Laravel API that powers the web application. We are happy to talk through whether a mobile app, a web app, or something else entirely makes more sense for your situation.

Discuss your mobile requirements →
Graphic Swish