Builder Prime is the kind of product where offline support is not a nice extra. It is part of whether the app is actually useful in the field.
When someone is clocking in at a jobsite, updating a daily log, or building out an estimate from a driveway with weak reception, “just retry later” is not good enough. The app has to keep the work moving, preserve state locally, and sync cleanly once the network comes back.
The local Builder Prime iOS repository is a good example of how to approach that problem in a way that stays understandable. The implementation centers on a queue-based offline architecture for two especially important workflows:
- clock in and clock out with daily logs
- estimate creation and editing, including projects, scopes, measures, scope items, discounts, finance options, and fees
Why offline had to be built into the architecture
Builder Prime is operational software for home-improvement teams. That changes the engineering bar immediately.
Field users are not always sitting on stable office Wi-Fi. They are moving between appointments, standing outside homes, walking properties, documenting work, and switching between customer conversations and production tasks. In that setting, an app that assumes clean connectivity turns ordinary work into failure handling.
The useful shift is to stop thinking about offline mode as a fallback. In products like this, offline support is part of the default product design.
The architecture is split into clear responsibilities
One thing the iOS implementation gets right is separation of concerns.
The offline system is not buried inside one giant view model. Instead, it is split across three layers:
- presentation views such as timer and task-related screens
- a business-logic layer centered on
DataStore - a persistence and networking layer centered on
RepositoryandNetworkMonitor
That split matters because offline behavior touches UI responsiveness, local persistence, queue processing, and eventual API synchronization. If all of that gets mixed together, the system becomes extremely hard to reason about.
NetworkMonitor decides when the app can really sync
The NetworkMonitor layer uses NWPathMonitor and also performs periodic health checks, which is a practical detail I like. Reachability alone is not always enough. The app needs a better signal for whether it should try to flush pending work.
Once connectivity is restored, the monitor publishes the change and triggers queue processing automatically. That means the user does not have to babysit synchronization.
DataStore protects the user experience
DataStore acts as the business-logic coordinator. It owns in-memory state, decides whether to take the online or offline path, updates local data immediately, and notifies the UI when something changes.
That is what enables optimistic UX. When a user performs an action offline, the app does not freeze or act like nothing happened. It updates local state right away and shows messaging that the change has been saved and will sync later.
Repository owns persistence and replay
The Repository actor is where the offline system becomes dependable instead of ad hoc.
It handles:
- storing snapshots for cached reads
- queueing pending write operations
- replaying queued operations when connectivity returns
- mapping local temporary IDs to server IDs
- refreshing or invalidating cache entries after successful sync
Using an actor here is important. Queue processing, local cache mutation, and ID resolution are exactly the kind of work that benefits from one serialized owner.
The two data models do most of the heavy lifting
The core offline story is built around two persisted models: Snapshot and PendingOperation.
Snapshot stores cached API responses by a generated request key. That lets the app support cache-first reads for things like daily logs, project scope, measure sheets, scope items, and other project data that users may need to reopen while offline.
PendingOperation stores deferred writes with details such as:
- method name
- encoded parameters
- request type
- priority
- retry state
- in-progress state
- creation time
That combination is what makes the system more than a temporary memory cache. Operations survive restarts, and the app can resume syncing without asking the user to repeat work.
How offline clock in and clock out works
Clock-in and clock-out flows are a good test of whether an offline system is honest about real-world conditions. They are time-sensitive, tied to location context, and easy to break if the app assumes the server is immediately available.
In Builder Prime, the flow is intentionally local-first:
- A timer or task-related view triggers a clock action.
HomeViewFactorybuilds theDailyLogObject.DataStore.sendDailyLog()checks connectivity.- If the device is offline, the app updates local state immediately.
- The daily log is written into cached snapshots and queued for later sync.
That gives the user immediate feedback instead of making them guess whether the action worked.
The clock-out side is especially interesting. When a clock-out event is created offline, the system preserves an offlineClockInId so the queued clock-out can still be paired with the original offline clock-in event later. That is a small detail, but it is exactly the sort of thing that separates a believable offline implementation from one that only works in demos.
The result is not just “we saved something locally.” The result is that the app keeps the semantic relationship between clock-in and clock-out intact until the server can assign final IDs.
How offline estimates are handled
Estimates are a harder problem than daily logs because they are not one record. They are a graph of related records with dependencies.
Builder Prime’s estimate flow supports offline work for:
- creating a new opportunity
- creating a new project
- adding and editing scopes
- updating measure sheets
- creating and editing scope items
- applying discounts
- updating finance options
- saving and deleting fees
The important design choice is that reads are cache-first and writes are optimistic.
If a user already loaded the relevant project data, the app can reopen that data from cached snapshots while offline. If the user edits that data offline, the app updates the cached state immediately and enqueues the write instead of blocking the action.
For new records, the repository creates temporary local IDs. In the estimate flow, those temporary IDs are negative values, which makes it easy to distinguish local placeholder entities from server-backed ones.
That means a user can do something like this while offline:
- create a new opportunity
- create a project that depends on that opportunity
- add scopes to that project
- add scope items and measurements under those scopes
And the app can still preserve the whole chain correctly until sync happens.
Queue priority is what keeps the sync logic sane
The queue is not just a list of requests to replay later. It is dependency-aware.
Builder Prime processes estimate-related operations in priority order:
Priority 3: createOpportunity
Priority 2: createProject
Priority 1: scopes, measures, scope items, discounts, finance options, fees
Within the same priority, operations are processed FIFO by creation time.
This is the part that keeps the system from collapsing under its own relationships.
If a user creates a brand-new opportunity offline and then immediately creates a project tied to it, replaying those operations in the wrong order would break the sync. By processing createOpportunity first, then createProject, and only then applying child updates, the repository preserves the dependency chain without forcing the user to care about it.
ID mapping is the real bridge between offline and online
Once the network is back, the repository does more than replay queued requests. It resolves local IDs into server IDs as each parent record is successfully created.
The iOS implementation keeps mapping dictionaries for exactly that purpose, including:
- opportunities
- projects
- scopes
- scope items
- measurements
- discounts
- finance options
- fees
- daily logs
That mapping layer is what allows a locally created project to stop being “temporary project -123” and become the real server project, while its dependent scope and item operations continue syncing against the correct final IDs.
This matters just as much for daily logs as it does for estimates. In both cases, the mobile app needs a stable local story first and a clean server reconciliation story second.
The UX payoff is bigger than the implementation details
The technical pieces are interesting, but the product payoff is the important part.
This architecture gives Builder Prime a few things that matter in day-to-day use:
- the UI responds immediately even when the network does not
- offline work survives app restarts
- cached project data stays usable in the field
- dependent records sync in the right order
- users do not have to manually reconstruct what they already entered
That is what real offline support should do. It should remove anxiety from the workflow, not just add another state the user has to understand.
What this reinforces for mobile product work
Builder Prime is a strong reminder that “offline mode” is usually the wrong mental model for field software.
The better model is this: design the app so that local state is trustworthy, server synchronization is eventual, and dependency handling is explicit. Once you do that, clock-ins, daily logs, estimates, scopes, and other job-critical actions can keep moving even when connectivity gets messy.
For us, that is the larger takeaway from the Builder Prime iOS implementation. Good mobile engineering for field teams is not only about screens or frameworks. It is about building a system that behaves calmly when the environment around it does not.