Inspiration

If you ship indie apps you get two bad options for knowing how they are doing. App Store Connect, which is delayed and coarse and tells you about downloads rather than people. Or a web analytics SaaS priced and designed for a company, where you pay per seat for dashboards built around marketing funnels you do not have. I wanted to open my phone and see what was happening in my own apps right now, and I wanted the free tier to be generous enough that a hobby app never costs anything.

What it does

AppGlance is three pieces: an SDK for Apple and Android platforms, a hosted ingest, and a native iOS dashboard app.

  • A live board. Sessions in flight, marked with a pulse dot in the Users ledger, refreshing over a websocket rather than a poll.
  • Push the moment something happens, and each alert wears the icon of the app it is about, not AppGlance's. An install from VitaView arrives as "VitaView / new user" carrying VitaView's artwork, which matters once you are running more than two apps.
  • Per-user stories. Open any user and get their sessions as a day-grouped timeline, so you are reading a person's path through the app instead of a bar chart.
  • Funnels, plus new-vs-returning comparison on any chart.
  • Install-origin attribution, so you can tell a genuinely new user from someone who redownloaded.

How we built it

Two SDKs, Swift and Kotlin, both with offline queueing and retry, both at 1.2.3 and published. A hosted Node ingest on Postgres. The dashboard is SwiftUI on iOS 26.

The per-app icon on a notification is the one piece of real trickery. iOS gives you exactly one way to replace the icon on an alert: build an INSendMessageIntent whose sender carries the artwork and hand it to updating(from:). The system then renders the sender's image where the app icon would be, and AppGlance's own icon becomes the small corner badge, which is the honest arrangement since this is an AppGlance notification about VitaView.

Subscriptions run through RevenueCat across five tiers, and the app reports the RevenueCat customer at every identity change so a subscriber's analytics identity and their billing identity stay joined up.

AppGlance dogfoods itself. The app reports to AppGlance, which is how both of the bugs below got caught.

Challenges we ran into

The sandbox lies about install dates. AppTransaction.originalPurchaseDate is always 2013-08-01 07:00 UTC in sandbox and TestFlight, and originalAppVersion is always "1.0". So every TestFlight install reported itself as a pre-existing user. Worse, the server writes that claim to every environment row sharing an install id, and the App Store build shares it, which meant TestFlight was poisoning production attribution. The fix was to return nil unless the transaction is .production.

Every first launch was counted twice. Diagnosed and then confirmed against real production data: 6 of 6 genuine first launches on the new SDK builds came through as split pairs, and the user record showed 2 sessions where there was obviously 1.

A replayed install could lose its origin forever. An event put back after a maybe-delivered failure (a 503, a timeout) went out under the same id, the ingest's ON CONFLICT DO NOTHING dropped the replay along with its metadata, and the origin was already marked sent. The client now tracks which ids a maybe-applied requeue put back and skips stamping those.

Accomplishments that we're proud of

  • 156 Swift tests and 153 Kotlin tests, zero failures, both SDKs lint clean.
  • Every one of those three bugs shipped with a regression test that was shown to fail with the fix disabled.
  • A free tier at 100k events a month, roughly 5x what the nearest comparable charges $8 for.

What we learned

Dogfooding found things no test suite was going to. All three of the bugs above surfaced because we were staring at our own numbers daily and something looked off. The other lesson is that sandbox and production differ in ways Apple does not document loudly, and any analytics claim derived from StoreKit needs an environment check before you trust it.

What's next for AppGlance

SDK 1.3.0 with the install-origin API, then Flutter and React Native wrappers.

Built With

Share this project:

Updates

Submission history