Motivation
VRVS bills clients from staff attendance. Clients said nobody showed. Staff said they did. There was no shared record either side trusted.
I built Vmanage for that. The agent checks in inside the site fence, works a checklist (photos, forms), and checks out. Managers open the visit in admin when someone disputes it. If the site has client emails set, those clients get a short confirmation after checkout: who came, when, what got done.
Field agents open this app every workday, site after site. If check-in or the checklist hangs mid-visit, they are stuck standing with the client. The app had to keep moving on the phones they actually carry.
How I built it
Flutter on the phone. React for admin. Spring Boot and MongoDB behind both so they see the same visit.
A day on the phone: open today's sites, walk up, check in. Check-in only succeeds if the phone is inside the fence ops drew for that site. Then the checklist: attendance, tools, photos, forms, whatever that visit type needs. Then checkout. Check-in GPS and checkout GPS are separate fields, so arrival and leave are two stamps.
Ops schedules visits, tracks misses, reviews history, approves schedule edits, and draws fences on the map. The fence is the building footprint the check-in has to land in. After check-in I preload the checklist while signal is still good, keep photos on the phone if upload is slow, and push them when the network returns.
Checkout triggers the reports. VRVS gets the full internal email. Configured clients get a confirmation with representative, site, times, and completed work. A partial checklist still sends that mail with whatever finished. Later disputes pull check-in location, fence status, checklist, checkout location, and those emails from admin.
Visit proof on the phone
Today's work → checklist on site → checkout. Approvals when a schedule changes.

Today's work
Challenges
Polygon geofences
Circles break when two customer sites sit on the same block. GPS noise drops the agent into a neighboring footprint and the visit follows that pin. Campuses are worse: one address, several buildings. The fence had to follow the real outline.
Check-in runs on the server. The phone sends lat, lng, accuracy. If the schedule has a building zone, I use that first. Otherwise the whole-site fence. Circles: distance to center. Polygons: point-in-polygon, and if outside I still store distance to the edge so an agent at the door and one two streets away leave different numbers. Allowed radius grows by reported accuracy (cap 100 m) plus a small ops buffer for drift near glass and concrete.
Hard mode blocks check-in outside the fence. Soft mode (default) writes OUT_OF_ZONE and the distance, then lets them through; the checklist with photos is the second stamp. New lead and site survey can skip the fence when there is no building yet. Ops draws shapes by dropping fence posts and finishing a polygon, saved as lat/long pairs on the site or zone.

GPS vs battery
Early builds kept a live location stream open. Phones died mid-shift. I removed continuous GPS and background mode. Location only wakes for check-in, checklist submit, and checkout.
Each of those is a short high-accuracy one-shot, then the provider drops back to power-save. High is enough for a fence check. Navigation-grade 1 s polling was waste. Sleep sits at 2 minutes / 100 m. I tried 10 minutes / 500 m first; every check-in cold-started and burned more battery.
Timed-out reads used to reuse whatever lat/lng was still in memory. An old home or office fix stamped people who were on site as out of zone by N meters. Fence checks now need a fix ≤45 s old. The UI can show older. If two reads overlap, the second waits and takes the in-flight result.
Low-end phones
Most agents are on cheap Androids with bad signal. A hung checklist with a client standing there kills the visit. The UI loads what is already on the phone. Sync updates it when the network is back.
Schedules and checklists open from cache, then refresh. At check-in I download the full checklist at the entrance while signal still works. Inside the building it usually does not. Photo uploads try for a few seconds on a weak link, then sit on disk and retry. Checkout jumps back to home while sync runs. The server fires visit report emails from that checkout call.
Bug I hit: the phone flipped "checked out" locally while the request was still queued. Agent walked to the next site. API rejected check-in until the old checkout finally replayed. The local flag now flips only after the server accepts checkout.
