Skip to main

Case study · AgTech · Internal tooling

Farm — offline-first field-sales app for agriculture

Published · Updated · By YuSMP Group Engineering

How we shipped Farm — a native iOS and Android internal app for an agricultural-goods distributor whose field-sales managers spend their days in cellars, warehouses, and out-of-coverage farms across the United States and the European Union. Built on an offline-first SQLite store, a Laravel + React back office, and a plan-vs-actual reporting layer that holds up to GDPR and CCPA scrutiny — and has stayed in continuous production use for three years.

IndustryAgTech · Field sales
Project year2022
EngagementFixed price + support
Farm — internal field-sales dashboard for agricultural distribution, plan vs actual across regions

The brief — a field-sales toolkit that works without signal

The Farm product team came in with a specific operational pain. Their regional sales managers covered farming territories across the United States and the European Union, and the daily workflow looked nothing like the assumption every off-the-shelf CRM mobile app makes. A rep would drive ninety minutes to a customer site, lose cellular signal inside a barn or a fertilizer storage cellar, talk through pricing for forty-five minutes, agree a deal, and then need to file it — frequently still out of coverage. Existing tooling either silently lost the report or required the rep to remember to refile it from the car. We rebuilt the workflow from first principles as a native iOS and Android internal app: an offline-first SQLite mirror of the product catalog, deal reporting that queues locally and reconciles deterministically, and a regional plan-vs-actual dashboard that the senior manager opens at the start of every week. The result is an internal sales toolkit that ships across the US and EU under GDPR and CCPA / CPRA expectations from day one, sits inside YuSMP Group's portfolio of mobile app development work, and has now run unattended in production for three years.

Project highlights

Native iOS & Android clients Offline-first SQLite catalog Conflict-free deal sync Plan vs actual reporting Regional dashboards in React Laravel admin back office Role-based access 3 years in production · US & EU

By the numbers

A snapshot of what the Farm build delivered across iOS, Android, and a Laravel + React back office in its first production cycle and the three operating years that followed.

2native platforms — iOS in Swift and Android in Kotlin, fully separate codebases optimized per OS
3 yrscontinuous production use across US and EU field-sales territories with stable telemetry
100%of deals filed offline reconcile deterministically on the next successful sync — no duplicates
9 moinitial build window from discovery to first production rollout on both stores
4core product categories — fertilizers, seeds, chemical agents, and equipment — in the structured catalog
14–20 wktypical delivery window for a comparable field-sales internal app on both stores
Farm structured product catalog — fertilizers, seeds and chemical agents with current pricing on iOS and Android

Why native iOS and Android over a cross-platform shell

The platform decision dominated every other architectural choice. We chose native Swift on iOS and Kotlin on Android over a single cross-platform shell because the trade-offs in a three-year production window line up cleanly with the native path. Native clients gave us first-class access to the platform-native Core Data and SQLite stack on iOS and Room over SQLite on Android — the foundation of any defensible offline-first mobile architecture. Background sync semantics differ enough between iOS BGTaskScheduler and Android WorkManager that wrapping both in a single abstraction would have cost us the predictability the operations team needed for unattended reps.

Cross-platform shells — React Native, Flutter, the Xamarin tail — were evaluated and eliminated. Their abstractions over background work and platform-native SQLite were the wrong shape for a rep workflow that spends hours offline. Going native meant the entire stack — UI, local store, sync engine, push-notification delivery — is owned per platform and citable end-to-end against Apple and Google's own developer documentation.

Native Swift / Kotlin vs cross-platform shells for a long-lived field-sales internal app
Dimension Native Swift / Kotlin (Farm) React Native Flutter
Offline SQLite accessFirst-class — Core Data and RoomBridged — WatermelonDB or third-partyBridged — sqflite or Drift
Background syncBGTaskScheduler / WorkManager directWrapped — variable reliabilityWrapped — variable reliability
Battery profile on long offline sessionsTuneable per platformHeavier — JS bridge costLighter than RN, still abstracted
Three-year maintenance costTwo stable codebases on OS-vendor APIsFramework upgrade churnFramework upgrade churn
Native accessibilityVoiceOver / TalkBack first-classAcceptable — partial gapsAcceptable — partial gaps
Push notification reliabilityAPNs / FCM directBridged — Notifee / RNFBBridged — firebase_messaging
Per-OS keyboard / locale behaviorOS-nativeWrapped — edge casesWrapped — edge cases

Platform references: Apple BGTaskScheduler documentation, Android WorkManager reference.

Farm deal-reporting flow — Swift / SwiftUI offline-first queue reconciling on iOS

iOS build — Swift, Core Data, and the deal-reporting flow

The iOS client is built in Swift with SwiftUI for the UI layer and Core Data over SQLite for the local store. The entire deal-reporting surface collapses into a single state machine — draft, queued, syncing, reconciled — and the rep never sees a network error in the user flow because the report has already been persisted locally before the screen finishes its dismissal animation. Customer search runs against the on-device store with no network round-trip, the catalog is paged into a lazy list, and pricing is resolved from the locally cached price-list version that was authoritative at the rep's last successful sync.

The reconciliation path is where most internal sales apps lose correctness, and where we spent disproportionate engineering effort. The flow is: write the report to Core Data with a monotonic client timestamp and a UUID idempotency key, queue it in BGTaskScheduler, attempt sync on every connectivity transition, and let the server return the canonical entitlement record. Duplicates are impossible because the idempotency key is the primary correlation, and a sale closed in a cellar in Sweden and reopened on a train two hours later reconciles cleanly. The end-to-end iOS surface is delivered as part of our iOS and Android engineering practice.

Farm Android offline sync engine — Kotlin Room store with deterministic conflict resolution across LTE handoffs

Android build — Kotlin, Room, and the sync engine

The Android client is written in Kotlin with Jetpack Compose for the UI and a Room layer over SQLite for the local store. WorkManager handles the background sync schedule with backoff semantics that respect Doze mode and Samsung, Xiaomi, OnePlus, and Pixel battery optimizers — without a foreground service, the sync engine would die quietly inside an aggressive OEM container within minutes, breaking the implicit contract that an offline report eventually shows up in the manager's dashboard. A minimal persistent notification keeps the long-form sync alive across Android 10 through Android 14 device families.

The sync engine is where the Android client earns its keep. The on-device delta is shipped to a Laravel API on every connectivity transition, the server returns the authoritative price list and customer roster, and conflict resolution is deterministic — server wins on pricing and customer records, client wins on field notes and rep-attached metadata. After a Wi-Fi to LTE handoff the engine resumes the last sync token automatically, and the catalog refresh is incremental rather than a full reload — a rep with a 250-MB local store does not re-download the catalog every Monday. The same engineering team carries iOS and Android in lockstep as part of our mobile app development practice.

Farm regional manager dashboard — plan vs actual by product category and region, Laravel + React back office

Back office, plan vs actual, and audit-ready posture

The Farm back office sits on a Laravel API with a React admin and holds only the data it must — customer roster, product catalog, price list, quarterly sales quotas, and the deal-report stream pouring in from the field. There is no per-rep location track, no idle-time keystroke capture, and no metadata pipe to a third-party observability vendor. Counters that drive the regional plan-vs-actual dashboard are aggregated server-side and shipped as anonymous numeric series; per-customer identifiers do not leave the customer table.

Billing identity for the distributor's own customers — the farms and agricultural cooperatives buying the product — is held in the same Laravel store, but is access-gated through a role-based permission model so a field rep sees only their territory's customers. Infrastructure-as-code policies enforce the access invariants — any pull request that would broaden a rep's read scope or introduce a per-rep location log fails CI. The posture is built to align with GDPR obligations for users in the European Union and CCPA / CPRA obligations for users in California and the broader United States — and to make a future independent readiness review a documentation exercise, not an architectural retrofit.

Compliance posture: GDPR-aligned · ISO 27001 ready · SOC 2 Type II in progress · HIPAA-capable · CCPA-acknowledged.

Delivery methodology

A five-phase build that took Farm from territory-research interviews to production across iOS, Android, and a Laravel + React back office.

Phase 1

Discovery & rep ride-along

Field shadowing with regional managers, offline-coverage mapping, GDPR + CCPA posture review for customer records, role-based access requirements per territory.

Phase 2

Catalog & sync model

Product catalog schema, structured pricing, sync token design, idempotency-key contract for deal reports, Laravel API skeleton, React admin scaffolding.

Phase 3

Platform builds

Swift / SwiftUI iOS client on Core Data + BGTaskScheduler; Kotlin / Jetpack Compose Android client on Room + WorkManager; deal-reporting state machine.

Phase 4

Audit-ready hardening

Infrastructure-as-code policies that block access-scope regressions, role-based permission QA, crash-telemetry pipeline, three-year operating envelope review.

Phase 5

Launch & telemetry

App Store + Google Play internal-distribution submission across US and EU storefronts, regional-dashboard rollout, plan-vs-actual cutover for senior managers.

Plan vs actual, quota assignment, and the manager view

Farm's reporting layer was built to keep field reporting and manager analytics provably aligned, because the plan-vs-actual loop falls apart the moment a manager and a rep see different numbers for the same customer in the same week. The quarterly quota is set by a senior manager in the React admin and pushed to each rep's local store on the next sync. The rep sees their own progress in the mobile app — a single sparkline against the plan, broken down by product category — and the senior manager sees the regional aggregate in the back office with a drill-down to per-rep and per-customer breakdowns. Server-side aggregation runs against a single authoritative deal-report stream that the mobile clients feed via the idempotency-key path, so the manager view and the rep view always reconcile to the same source of truth. Kill-edit behavior, late-arriving offline reports, and rep-attached field notes all read from the same record, so a single deal resolves cleanly across reinstalls, device swaps, and the eventual web portal. The whole subsystem was built with extensibility in mind: adding a multi-currency tier, a per-territory commission engine, or a B2B partner-rep extension is a configuration change against the back office, not a code release.

Launching across the United States and the European Union

Farm launched on Apple App Store and Google Play (internal distribution) with storefronts active across the United States and the European Union. The English-language build serves field-sales reps in California, New York, Texas, Florida, and Washington in the US, and in the Netherlands, Germany, France, Ireland, and Sweden in the EU, without a separate codebase per region. Consent flows are region-aware at the back-office layer: rep accounts in the EU and EEA receive a GDPR-style granular consent screen for any optional product telemetry, and accounts in California receive a CCPA-style "Do Not Sell or Share My Personal Information" disclosure in the same enrollment flow. Data-handling practices are aligned with GDPR for European users and with the US state-privacy patchwork — CCPA / CPRA (California), VCDPA (Virginia), CPA (Colorado), CTDPA (Connecticut), UCPA (Utah), TDPSA (Texas), and Oregon CPA. Because the architecture minimises per-rep tracking data, regional compliance reduces to honest disclosure rather than per-jurisdiction data segregation.

The Laravel API was rolled out across EU and US regions in parallel — Netherlands, Germany, France, Sweden, and Ireland for EU coverage; US East and US West for North America — with each region's stateless workers provisioned identically. The aggregation service that drives the regional plan-vs-actual dashboard runs stateless workers that can be pinned to EU or US regions independently for future data-residency commitments. Both the App Store and the Google Play distribution were calibrated for internal-enterprise enrollment, and the in-app privacy disclosure documents exactly the architecture above, citing GDPR obligations and California CCPA obligations directly. The engineering team behind the build sits across CET and runs a CET workday with East-Coast US overlap (9 AM–1 PM ET) for stand-ups, store-review choreography, and incident response — the timezone that lets a US product team and an EU engineering team share four hours of live overlap every day.

Tech stack and roadmap

Swift SwiftUI Core Data BGTaskScheduler Kotlin Jetpack Compose Room WorkManager SQLite Laravel PHP 8 React PostgreSQL Redis APNs Firebase Cloud Messaging Docker Terraform Prometheus

The active custom software development roadmap for Farm includes a per-territory commission engine, a multi-currency price list for cross-border distributors, an obfuscated offline-share transport so reps can hand a deal report from one device to another in the field without round-tripping through the server, and a desktop client built on Tauri to share business logic with the mobile codebase. A B2B partner-rep tier with bring-your-own-customer-list, team management, and SSO is planned for US and EU mid-market distributors, with the entitlement subsystem already structured for multi-seat assignment. Infrastructure plans include further cloud & DevOps automation of the regional-rollout pipeline, an internal continuous-verification harness for the offline-sync correctness contract, and a future independent readiness assessment scaffolded into the operations cadence.

Build a field-sales app like this — talk to us

If you are planning an internal field-sales mobile app, an offline-first AgTech tool, or any mobile app where reps spend hours out of coverage and the reporting loop has to survive an outside auditor for audiences in the US and EU, we have shipped this stack end-to-end and can compress the build timeline meaningfully. The live product is operated internally by the distributor at yusmpgroup.ru/cases/farm (case page), and the engineering team behind it sits inside YuSMP Group. We work fixed-price for well-scoped MVPs and on dedicated development teams for ongoing delivery, with a CET workday and a guaranteed East-Coast US overlap (9 AM–1 PM ET) window for stand-ups, demos, and incident response.

Book a discovery call See mobile development services

Frequently asked questions

How much does it cost to build an internal field-sales mobile app for distributors?

An internal field-sales mobile app with iOS and Android clients, an offline-capable product catalog, deal reporting, and a Laravel or Node back office typically costs $80k–$180k for an MVP. Adding regional plan-vs-actual dashboards, role-based permissions, conflict-free offline sync, push reminders, and integrations into an existing ERP or 1C-style accounting stack brings a full-featured rollout to $220k–$450k. The dominant cost driver is the offline-sync correctness work and the per-region pricing logic.

Why use a native iOS and Android build instead of a cross-platform framework?

Field-sales reps in agricultural territories work for hours offline, in cellars, warehouses, and fields with no signal. Native Swift and Kotlin clients let us control battery use during long sync sessions, integrate platform-native SQLite stores cleanly, and ship per-OS keyboard and accessibility behavior that React Native and Flutter still wrap in compromises. With a three-year operating window in mind, the maintenance cost of two native codebases turned out lower than the cumulative cost of working around cross-platform abstractions.

How do you build a true offline-first mobile sales app?

A defensible offline-first posture is an architecture decision, not a feature toggle. The full product catalog, pricing tables, and customer list are mirrored to a local SQLite store at first login and incrementally updated on every successful sync. Deal reports written offline are queued with monotonic local timestamps and reconciled on the server using idempotency keys, so a rep who closes a sale in a basement and reopens the app on a train two hours later never duplicates or loses a transaction. Conflict resolution is deterministic — server wins on price, client wins on field notes.

What does the Farm app do for field sales managers in agriculture?

Farm is the internal toolkit a regional sales manager carries into a farm visit. It exposes a structured catalog of fertilizers, seeds, chemical agents, and equipment with current pricing; lets the rep file a deal report in seconds against a known customer; tracks plan vs actual against the rep's quarterly sales targets; and surfaces a manager-side dashboard that breaks the numbers down by product category and region. Push reminders nudge unfiled reports, and a back-office admin in React handles catalog updates, pricing changes, and quota assignment.

How long does it take to ship an internal field-sales app on iOS and Android?

A focused MVP with native iOS and Android clients, an offline-first catalog, deal reporting, and a Laravel admin typically takes 14–20 weeks. Adding the manager-side regional dashboard, the plan-vs-actual analytics, role-based permissions for senior managers and field reps, and the conflict-free sync layer adds 6–10 weeks. Hardening the app for three years of unattended production use — crash telemetry, push reliability, sync replay logs — is frequently underestimated and should be budgeted at 4–6 weeks of dedicated work.

Share this case

LinkedIn X

Plan a similar build

Book a discovery call