Delivered remotely for a business based in Manchester, United Kingdom. Names withheld by agreement.
Project Overview
Industry: Field service and project operations for a services contractor.
Type of solution: A cross-platform mobile application built directly on managed cloud services — hosted identity, a document database, object storage, push messaging and a single serverless function.
Business context: A contracting business coordinating crews across many concurrent job sites. The operational record lived where it usually lives in such businesses: in phone calls, message threads, paper folders in vehicles, and a spreadsheet someone maintains. That works until the business grows past the point where one person can hold it in their head — at which point nobody can say with confidence which sites are active, who is booked where tomorrow, who moved a customer's appointment, or where the current permit copy is.
General users: Field crew members and office coordinators, plus administrators with elevated permissions over submitted records.
General purpose: To put the whole operational record — jobs, appointments, documents, photographs, receipts and the history of changes to all of it — into one application that works from a job site on a phone.
The Business Challenge
There is no single view of the work. Active sites, scheduled visits and crew allocation live in several places at once, none of them authoritative. Every question about the schedule becomes a phone call.
Appointments move constantly, and the history disappears. Site visits get rescheduled several times before they happen. When a customer, a manager or an inspector asks who changed a booking and when, an editable field on a record holds only the current answer. The question that actually matters — how did we get here — has no answer at all.
Job paperwork is scattered. Plans, bids, permits and site photographs live on individual phones, in email threads and in vehicle folders. They are needed on site, which is exactly where they are least likely to be available.
Photographs are operational evidence, not incidentals. Site condition before and after work is the record a dispute turns on. Captured to a personal camera roll, that evidence is unattached to the job and effectively unretrievable.
Field expenses arrive late and unattributed. Receipts photographed into a message thread reach the office days later with no reliable link to who submitted them.
People are not at a desk. The users are in vehicles and on sites, frequently on poor connections, holding a phone. A web application accessed on a laptop back at the office would have been ignored, because the moment the data needs capturing is the moment nobody is at a laptop.
The business cannot operate infrastructure. A contracting company has no appetite to run servers, patch them, or be paged when they fail. Whatever was built had to be operable by a business whose expertise is entirely elsewhere.
Our Approach
Make the scheduling history immutable, not editable. Rather than treating an appointment as a record whose date field gets overwritten, we treat every scheduling action as an event. Creating a booking writes an entry. Moving it writes another. Nothing is ever updated in place, and every entry carries the acting user and a timestamp assigned by the server rather than by the device. The result is a plain-language history — scheduled by whom for when, then rescheduled by whom for when — that can be read directly off the screen when someone asks.
Build directly on managed services rather than a bespoke server. For a business of this shape, a custom API tier would have been infrastructure to operate for no functional gain. The application authenticates against a managed identity provider and works directly with a hosted document database and object storage, with a single serverless function for notification dispatch. There is nothing to provision, patch or monitor.
Design the data model around the queries the calendar actually makes. Document databases have no server-side joins and constrained compound queries, so we designed for the reads rather than retrofitting them: appointments carry a purpose-built date key alongside the true timestamp so month and week views execute as indexed range queries rather than fetching everything and filtering afterwards, and appointments hold a native reference to their job so the relationship is one the database itself understands.
Treat time zones as a first-class correctness problem. A scheduling product that is subtly wrong about dates is worse than no scheduling product. Each appointment is stored with an explicit separation between its absolute moment and the local calendar day it belongs to, so day grouping, calendar highlighting and range queries agree regardless of the device's time zone.
Categorise documents at the point of capture. Rather than a general attachments list that becomes a pile, files are filed as plans, bids, permits and notes, or photographs, at the moment they are added — with capture-source and file-type validation appropriate to each category. Filing is a decision made once, on site, by the person who has the context.
Archive rather than delete, and make it personal. A user retiring a job from their working list should never destroy anyone else's context. Archiving is per-user and reversible, and the underlying record persists.
Build the calendar we needed. The weekly view required interaction behaviour no available component provided, so it was brought into the codebase and rewritten rather than working around a dependency that was close but wrong.
Keep the schema disciplined despite the datastore not enforcing one. Every collection and every field is referenced through a central registry of named constants rather than string literals scattered through screens — the practice that keeps a schema-less database maintainable as a product grows.
The Solution
Job records. Each site is a record with the client business, full address, unit, project manager and the user who created it, editable with the editing user retained on the record, and searchable by text across the working list.
Shared scheduling calendar. A single calendar of on-site appointments across the whole operation, in month or week view. Selecting a job when booking auto-populates the site address and project manager, so a booking is a few taps rather than re-entry.
Immutable scheduling history. Every booking and every reschedule appends an entry recording who acted, the appointment date and time set, and a server-assigned timestamp. The history for any appointment is readable in plain language in the application.
Next-appointment visibility per job. Each job surfaces its next upcoming site visit, computed from that job's future calendar entries.
Categorised document capture. Plans, bids, permits and job notes, and site photographs, attached to a job from the device camera, photo library or document picker — with upload progress, file-type validation appropriate to each category, and in-app full-screen viewing with pinch-zoom for images.
Field expense receipts. Receipts captured from camera, library or file picker, attributed to the submitting user and grouped by that user for office review, with human-readable file sizes and controlled deletion.
Broadcast notifications. When a job is created or edited, a push notification goes to the team and a durable notification record is written, so the event survives a dismissed banner.
Notification inbox with read state. Notifications are listed as unread and read, with per-user read and dismissal state, a live unread badge recalculated when the application returns to the foreground, and a detail view.
Notification-driven navigation. Tapping a notification routes directly to the relevant record — from a cold start, a backgrounded app or an active session, on either platform.
Personal archiving with restore. Jobs can be archived out of a user's working list and restored later, without affecting anyone else.
Account management. Email and password sign-in against a managed identity provider, password reset by verified email link, and a password strength policy enforced at entry.
Key Features
Append-only scheduling audit trail Every booking and reschedule writes an immutable entry with the acting user and a server-authoritative timestamp, producing a readable history of how an appointment reached its current date rather than only what that date is.
Shared operational calendar with month and week views One calendar across all active sites, with the visible window driving the query so each view retrieves only its own date range.
Job-linked appointments with auto-population Appointments reference their job natively, and selecting the job when booking fills in the site address and project manager automatically.
Categorised on-site document and photograph capture Plans, bids, permits and notes, and photographs, filed into the right category as they are captured from camera, library or document picker, with validation appropriate to each.
Field expense receipt collection Receipts captured on site, attributed to the submitting user and grouped for office review, with deletion controlled by ownership and administrator status.
Durable notifications with per-user read state Team-wide alerts on job creation and edits, persisted as records with per-user read and dismissal markers and a live unread badge, so nothing depends on a banner being seen.
Notification-driven deep navigation A tapped notification routes into the correct screen from any application state, including a cold launch, on both platforms.
Time-zone-correct scheduling Each appointment separates its absolute moment from the local calendar day it belongs to, so grouping, highlighting and range filtering stay consistent across devices.
Per-user archiving with restore Jobs can be retired from a personal working list and brought back, without removing them for anyone else or destroying the underlying record.
A purpose-built weekly calendar component The weekly view was brought into the codebase and rewritten to provide interaction behaviour that no available component offered.
Technical Architecture
Mobile client. A cross-platform React Native application composed as a drawer containing a bottom tab navigator containing four independent navigation stacks — jobs, calendar, notifications and receipts — with an imperative navigation service that lets notification handlers route into any stack from any application state, including before the navigator has mounted. Session, badge and screen state are held in Redux. Device capabilities cover camera, photo library, document picking, image cropping and zoomable viewing.
Identity. A managed authentication provider handles credential storage, verification, password reset by email and repeated-failure protection. The application never stores credentials itself.
Data layer. A hosted document database holds jobs, appointments, scheduling log entries, file metadata, receipts, notifications, per-user read and dismissal markers, archive entries, users and the administrator registry. Relationships are expressed as native document references. Calendar reads are bounded by explicit date ranges against a purpose-built date key so filtering runs in the database index. Metadata timestamps are assigned by the server.
File layer. Documents, photographs and receipts are uploaded directly from the device to managed object storage and organised into per-submitter folders, with a metadata record written to the database. Binaries never transit the database, and deletion removes both the stored object and its record.
Messaging layer. A single serverless HTTP function obtains a scoped credential for the messaging service and dispatches notifications to devices. The client handles delivery differently by application state: a synthesised local notification in the foreground, a registered notification channel on Android, and headless-launch handling on iOS.
Presentation. A centralised layer of colours, dimensions, typography components, image assets and copy, and a central registry of collection and field names so the schema-less datastore is addressed consistently everywhere.
Flow: Mobile application → Managed identity provider → Hosted document database (jobs, appointments, audit log, metadata) → Managed object storage (documents, photographs, receipts) → Serverless notification function → Push delivery to devices
Technology Stack
| Category | Technology |
|---|---|
| Mobile framework | React Native with React 18 |
| Navigation | React Navigation — stack, bottom tabs, drawer, material top tabs, plus an imperative navigation service |
| Client state | Redux with redux-thunk |
| Identity | Firebase Authentication (email/password, password reset) |
| Database | Cloud Firestore (document database with native document references) |
| File storage | Firebase Cloud Storage |
| Push messaging | Firebase Cloud Messaging with platform push handling on iOS and Android |
| Serverless | Firebase Cloud Functions on Node 18, using the Firebase Admin SDK and Google Auth Library |
| Calendar | react-native-calendars for the month view; a vendored, customised weekly calendar component |
| Media capture | Image crop picker, document picker, fast cached image rendering, zoomable image viewer |
| Input components | Modal date and time pickers, native picker select, action sheets, keyboard-aware scrolling |
| Animation & UI | Reanimated 3, gesture handler, native screens, safe area context, splash screen |
| Date handling | Moment with explicit UTC and local separation |
| Local persistence | AsyncStorage for session flags and device registration |
| Connectivity | Network state detection with user-facing offline handling |
| Dependency management | patch-package for maintained dependency fixes |
Technical Challenges & Solutions
| Challenge | Our Approach |
|---|---|
| Appointment history disappearing every time a booking is moved | Scheduling is modelled as immutable events rather than an editable field. Every creation and reschedule appends an entry carrying the acting user and a server-assigned timestamp, rendered as a plain-language history that answers "who moved this and when" directly. |
| A document database with no server-side joins driving a calendar | The data model was designed around the reads it has to serve: a purpose-built date key alongside the true timestamp lets month and week views run as indexed range queries, and appointments hold native references to their jobs rather than loose identifiers. |
| Resolving a job for every appointment in a month without serialising the round trips | Reference lookups are issued together and resolved in parallel rather than awaited one at a time inside the loop, so the calendar's load time is set by the slowest lookup rather than their sum. |
| Scheduling that must be correct across device time zones | Each appointment separates its absolute moment from the local calendar day it belongs to, so day grouping, calendar highlighting and range filtering agree regardless of where the device is or how it is configured. |
| Notifications behaving differently in foreground, background and terminated states on two platforms | State-specific handling throughout: a synthesised local notification when the app is in the foreground, a registered Android notification channel, iOS headless-launch handling, and an imperative navigation service so a tap can route into a deeply nested navigator that may not yet be mounted. |
| An unread badge with no server to compute it | Unread count is derived on the client from notification records against per-user read and dismissal markers, recomputed on every return to the foreground so the badge is correct when the user actually looks at it. |
| One user tidying a shared job list destroying another user's context | Archiving is per-user and reversible rather than global and destructive; the active list is the shared collection minus that user's own archive, and the underlying record always persists. |
| No available weekly calendar component with the required interaction model | The component was brought into the repository and rewritten to the behaviour the product needed, rather than constraining the product to what a dependency happened to offer. |
| Keeping a schema coherent in a database that does not enforce one | Every collection and field is referenced through a central registry of named constants rather than string literals in screens, so a schema change is a single edit rather than a search across the codebase. |
Security & Reliability
Managed identity. Authentication runs against a managed provider that handles credential storage, verification, password reset by verified email link and protection against repeated failed attempts. The application itself never stores or transmits raw credentials.
Password policy at entry. Strength requirements covering length, mixed case, digits and symbols are enforced when a password is set.
Immutable audit trail. Scheduling history is append-only. Entries are never modified or removed, so the record of who changed an appointment and when cannot be quietly revised.
Server-authoritative time. Audit entries and record metadata take their timestamps from the server rather than the device clock, so history cannot be back-dated by changing a phone's settings.
Attribution on every record. Jobs retain both their creator and the user who last edited them; scheduling entries, uploaded files and receipts all retain their submitting user.
Non-destructive removal. Jobs are archived rather than deleted, per user and reversibly, so operational history is not lost to a tidy-up.
Elevated permissions held separately. Administrator status is held in its own registry rather than inferred from a flag on a user record, keeping the elevation explicit and auditable.
Managed platform reliability. Data, files, identity and push delivery all run on managed cloud services with their own availability and durability guarantees, and the only server-side code is a stateless function that scales on demand — there is no server for the business to operate, patch or be paged about.
Connectivity awareness. Network state is detected and surfaced, so a field user on a poor connection is told what is happening rather than left with a silent failure.
Scalability & Performance
Managed, elastically scaled backend. The document database, object storage, identity provider and messaging service all scale independently of anything the business operates, and the notification function is serverless — capacity planning is not a task anyone has to perform.
Query-shaped data model. Calendar views are bounded by explicit date ranges against a purpose-built date key, so a month view retrieves that month rather than retrieving everything and discarding most of it in the client.
Parallel reference resolution. Related records are fetched concurrently rather than sequentially, keeping list and calendar load times bounded by the slowest lookup rather than their sum.
Binaries out of the database. Documents, photographs and receipts go straight from the device to object storage and are served from there, so file size never affects database performance and uploads never pass through an application tier.
Cached image rendering. Repeated thumbnails render through a caching-optimised image component — significant in a product where a single job may carry many site photographs.
Debounced search. Text search over the job list is debounced so typing does not generate a request per keystroke.
A smaller active working set. Archived jobs are excluded from active views, so day-to-day screens stay proportional to current work rather than to accumulated history.
Native-backed navigation and lists. Native screen primitives and a native-driven animation library keep navigation transitions and list scrolling smooth on the mid-range devices field users actually carry.
Business Outcomes
- One authoritative view of the work, replacing a schedule assembled from calls, messages and a spreadsheet with a shared calendar every user sees.
- Scheduling changes are answerable, because every booking and reschedule is recorded permanently with the person who made it and when — the question customers and managers actually ask now has an answer in the application.
- Job documentation stays with the job, filed by category at the moment of capture rather than scattered across personal devices and email.
- Site photographs become retrievable evidence, attached to the job they document instead of accumulating in a camera roll.
- Field expenses reach the office attributed and promptly, captured on site by the person who incurred them.
- Changes reach the team as they happen, through push notifications backed by durable records so a dismissed banner does not mean a missed change.
- Capture happens where the work happens, on a phone at the site, rather than being deferred to an office visit that may not occur.
- No infrastructure for the business to run. A managed backend means no servers to provision, patch or monitor — a business whose expertise is field work is not asked to acquire operational expertise it does not want.
Why it worked
Operational software for field businesses fails in a specific and predictable way: it asks people who are holding tools to do data entry, and they decline. The application is then blamed for being wrong, when what it actually was is inconvenient at the only moment that mattered. The design constraint is not feature coverage — it is whether capture takes a few taps in a vehicle between sites.
Our team builds to that constraint. We put the whole operational record on the phone, made booking a site visit auto-populate from the job rather than asking for the same address twice, and made document filing a decision made once at the point of capture instead of a sorting task deferred to someone at a desk.
We also modelled the part of this domain that most systems get wrong. Scheduling is not a date field — it is a history, and in a contracting business the history is what gets disputed. We made it immutable, attributed and server-timestamped, so it holds up when someone asks.
And we chose the architecture the business could actually live with. A managed, serverless backend meant the client got a real application without acquiring a server estate, an on-call rota, or a patching schedule. Our teams work across React Native, managed cloud backends, offline-tolerant mobile data design, push notification delivery across platforms and application states, and the domain judgement to know which parts of an operational process must be captured faithfully and which are friction that will simply be worked around.
Final Summary
A services contractor was running crews across many concurrent sites on an operational record made of phone calls, message threads, paper folders and a spreadsheet. It held together until it did not: nobody could say authoritatively which sites were active, who was booked where, who had moved a customer's appointment, or where the current permit copy was.
Our team built a mobile application that holds the whole record. A shared calendar shows every site visit across the operation in month or week view, with appointments linked to their job so booking auto-populates the address and project manager. Every booking and every reschedule appends an immutable entry carrying the acting user and a server-assigned timestamp, so the history of how an appointment reached its current date is readable in the application rather than reconstructed from memory. Plans, bids, permits and site photographs are captured from the device and filed by category against the job. Field expense receipts are submitted on site, attributed to the person who incurred them. Job changes reach the team as push notifications backed by durable records with per-user read state, and tapping one routes straight to the record from any application state.
It runs entirely on managed cloud services — hosted identity, a document database with a model designed around the queries the calendar actually makes, object storage holding files outside the database, and a single serverless function for notification dispatch. There is no server for the business to operate. For a contracting company whose expertise is field work, that is not a technical detail; it is the difference between a system they own and a system that owns them.