---
title: "CoordKit"
description: "A field-grade coordinate instrument with six formats, offline measurement and a stamped camera, built around a map you aim rather than poke. Its most interesting code is the test suite: the platform rules that cost me a debugging session each are encoded as invariant tests that fail the build."
url: "https://dewmina.dev/projects/coordkit"
canonical: "https://dewmina.dev/projects/coordkit"
kind: "PRODUCTION"
year: "2026"
status: "Live on the App Store · v1.0"
role: "Founder and sole engineer: product, app, design system and release"
author: "Dewmina Udayashan"
---

# CoordKit

A field-grade coordinate instrument built around a map you aim rather than poke.

A field-grade coordinate instrument with six formats, offline measurement and a stamped camera, built around a map you aim rather than poke. Its most interesting code is the test suite: the platform rules that cost me a debugging session each are encoded as invariant tests that fail the build.

**My role:** Founder and sole engineer: product, app, design system and release

**Status:** Live on the App Store · v1.0

**Links:** [App Store](https://apps.apple.com/app/coordkit-mgrs-utm-map-gps/id6810684377)

**Stack:** Flutter, Dart, Riverpod, Google Maps, RevenueCat, Firebase, SQLite, Codemagic

## By the numbers

- **6** Coordinate formats, with smart detection on input
- **0** Advertising SDKs: the framework was removed, not configured off
- **5** Instrument modes sharing one panel and one map

## The problem

Every coordinate app puts a pin under your thumb. You tap, the finger covers the number you came to read, and the fix you get is wherever your fingertip landed, not where you meant. Worse, the moment a surveyor, a driller or a pilot needs MGRS, a stamped photo or an area measurement, the consumer apps stop and the professional ones start at a subscription price with a training course attached. CoordKit had to be precise enough to trust in the field and simple enough to open once, in the rain, with one hand.

## The approach

- **A fixed reticle.** The map moves under a crosshair that never moves. The map's bottom padding equals the panel height, so the camera's own centre *is* the reticle's point. There is no offset arithmetic anywhere in the codebase, and the coordinate is a continuous edit rather than a discrete guess.
- **Modes, not routes.** Position, Convert, Measure, Compass and Speed swap one panel's contents while the map and the framed point stay put, with no navigation between tools and no state to rebuild when you switch.
- **Honest precision.** The app prints only the decimal places the device's reported accuracy justifies, and names which source an elevation came from, because a barometric altitude and a surveyed height are different claims about the world.
- **Invariants as tests.** Every platform trap that cost a debugging session became a test that fails the build: banned APIs, disposal rules, sheet safe-area, and a cross-check that the code never requests a permission level the Info.plist has no string for.

## System architecture

- **Sensing.** GNSS stream, magnetometer with interference detection, barometer and a terrain-dataset elevation source with named provenance
- **Coordinate kernel.** Decimal degrees · DMS · DD N/S/E/W · UTM · MGRS · Plus Codes, with smart detection on paste in either direction
- **Measurement.** Spherical-model distance and area, live from the framed point rather than from tapped pins
- **Field recorder.** Coordinate-stamped capture burned into the image and its EXIF, plus GPX, KML and CSV in both directions
- **Invariant test suite.** Build-failing tests for banned APIs, controller disposal, sheet safe-area and permission-level/usage-string agreement

## Engineering decisions

### The map is an input device, not the answer

Treating the readout as the product rather than the map is what makes the fixed reticle possible, which makes a permanent bottom readout possible, which makes tools into modes of that panel instead of separate screens. One decision, and the whole navigation model falls out of it.

### Encode the trap, not the lesson

A comment saying 'don't use .when here' survives exactly as long as the person who wrote it. Riverpod 3 retries a failed provider, so a failure settles as loading-with-error and `.when` picks its loading branch forever. That is now a test that fails the build if `.when(` reappears in `lib/`. Same for disposing a `TextEditingController` as a local, for sheets that skip `useSafeArea`, and for asking iOS for a photo-library access level with no matching usage string, which does not throw but terminates the process.

### No advertising, at all

The ad SDK was removed outright rather than configured off. A linked ad framework still ships a sample app id in both manifests and still forces a tracking permission string for a prompt the app never shows. Pro is the only revenue, and the privacy label is honest because there is nothing to disclose.

### A sibling product, not a rewrite

CoordKit and Latitude Longitude Finder share a problem space and a private `coordinate_kit` package, but are independently designed. Two distinct products can rank for the same searches; one product with two skins cannibalises itself and confuses both listings.

## The map is a picker; the readout is the product

Most mapping apps treat the map as the answer. CoordKit treats it as an input device. The camera has bottom padding equal to the panel height, so the camera's own centre *is* the crosshair's point. There is no offset arithmetic anywhere in the codebase, and a pan is a continuous edit of the coordinate rather than a discrete guess.

That one decision cascades. Because the point is framed rather than tapped, the readout can live permanently at the bottom of the screen without ever being obscured. Because the readout is permanent, the tools can be *modes of that panel* instead of separate screens. Position, Convert, Measure, Compass and Speed swap its contents while the map, the framed point and the coordinate stay exactly where they were.

## Precision you can argue with

A consumer GPS receiver will happily hand you eight decimal places of latitude while being twelve metres wrong. CoordKit refuses to print digits the fix does not support: the number of decimals shown is derived from the accuracy figure the device reports, so the readout degrades visibly when the sky closes in.

Elevation follows the same rule. The app prefers a public terrain dataset, falls back to the device's own altimeter, and **tells you which one it used**, because a barometric altitude and a surveyed height are different claims about the world.

## The test suite is the architecture document

The most transferable thing in this codebase is not a feature. It is the set of tests that exist purely to stop a specific class of bug from ever returning.

- Riverpod 3 retries a failed provider, so a failure settles as `AsyncLoading` *with* an error, and `.when` picks its loading branch forever. A test fails the build if `.when(` reappears in `lib/`.
- A `TextEditingController` disposed right after `await showDialog` looks correct and is not. The route keeps rebuilding its subtree through the *exit* animation, so a still-animating `TextField` uses a dead controller and throws a frame later, on a device, never in a build log. A test enforces that these are state fields.
- Scroll-controlled sheets must set `useSafeArea: true`, or a sheet that grows to full height puts its top under the status bar. Enforced.
- A native permission mismatch **kills the process**; it is not a catchable exception. iOS terminates an app that requests an access level with no matching usage string, and naming a photo album silently escalates the library request to read-write. A test cross-checks what the code asks for against what the manifests declare.

None of these are things a linter catches or a code review reliably remembers. Writing them down as prose would have been a document nobody re-reads. Writing them as failing tests makes the knowledge outlive the person holding it, which is most of what architecture is for.

## What shipped

Six coordinate formats (decimal degrees, DMS, DD N/S/E/W, UTM, MGRS and Plus Codes) with smart detection on input, so pasting anything recognisable works. Distance and area measurement on a spherical model. A compass that flags magnetic interference instead of quietly pointing the wrong way. Speed with trip distance and a moving average. A camera that burns coordinates, elevation and time into the photo *and* its EXIF metadata, for when a screenshot is not evidence. Saved points with GPX, KML and CSV in both directions.

No advertising, at all. The ad SDK was removed outright rather than configured off, because a linked ad framework still ships a sample app id in both manifests and still forces a tracking permission string for a prompt the app never shows. Pro is the only revenue.

---

Written by Dewmina Udayashan. Full site: https://dewmina.dev
