Skip to main content

App versioning

How it works code-wise​

  • The key question is where all of this logic will "live". It has to live inside main, so that we can control not only initialization but also when the app goes into the background.
  • The next step is to build a policy: a file that essentially holds the rules of this flow (for example, which app statuses will exist).
  • For that we create a file that essentially looks at what applies in each case (based on our policy) and acts accordingly in the app by showing the appropriate page.
  • There are 2 pages. One that blocks the user completely, where the update is mandatory, and one where the update is optional and they can keep navigating.
  • Each of these pages, whether it skips or updates, notifies the corresponding provider.
  • The provider is the one that updates state based on the responses it gets from the service.
  • The service calls the endpoint with the appropriate headers and without a token.

When we check​

  • On cold start, immediately after the first frame — never blocking the splash screen.
  • On every resume from the background.
  • At most one successful check every 15 minutes. Resuming more often than that does nothing.
  • Never on login, never tied to a route — a user who has been logged in for months is still reachable.

What the server can say​

  • ok — nothing happens.
  • optional — a dismissible card at the bottom, with Skip + Update.
  • required — full screen block, no Skip, no back button.
  • maintenance — full screen block with Retry instead of Update, regardless of which version the user is running.

How it decides​

  • The server decides whether to send a status — preferred, because staged rollouts and per-customer exceptions stay server-side.
  • Otherwise we compare build numbers: anything below minimumBuild blocks.

When something goes wrong​

  • A failed check never blocks — timeout, 500, bad JSON, no connection at all: they all leave the app fully usable.
  • Only a successful response can block. A backend outage cannot block the app (VERY IMPORTANT!! The call is made every time the app opens, and that can happen in places with no signal, such as a tunnel).
  • The version endpoint does not need a token, so we can still reach a build that is too old to log in (TO BE DISCUSSED).

Offline​

  • The last successful verdict is cached, so a block survives airplane mode and restarts.
  • It stops applying after 7 days, so that someone genuinely off-network does not stay trapped (for safety reasons).
  • We cache the raw document, not the verdict — that way it is re-evaluated correctly if the app version changes.

Notes on offline​

For a verdict of "Blocked", what actually happens is that we store the whole JSON response ({"minimumBuild": 290}) instead of the verdict text BLOCKED. This covers the following case: I open the app -> it tells me an update is required -> I update -> I open the app offline. With the word BLOCKED stored, that is what would have been saved and it would block us again. Now that the JSON is stored, the comparison can be made locally.

The optional case​

  • Skip suppresses only that version; a newer one asks again.
  • The choice persists across restarts.

While the app is blocked​

  • The block is drawn on top of the live app, not in place of it.
  • Nothing is torn down — providers, uploads and BLE sessions all keep running.
  • Trips do not have live tracking yet. Once they do, they will keep recording underneath.
  • Tapping Update opens the store but does not lift the block — only the next check does, once the new build is installed.
  • It covers everything, though I am not entirely sure about the OTP unlock deep link (TO BE DISCUSSED).

Every API call​

  • Sends X-App-Version: 1.9.96+283 and X-App-Platform.
  • Gives the backend a live picture of what is running out there, per customer.
  • Lets the backend reject old builds with a 426 (TO BE DISCUSSED, regarding the status code).

Testing​

  • VERSION_INFO_MOCK=required | optional | maintenance | ok | localized in .env.
  • It applies with hot reload; VERSION_INFO_URL needs a hot restart.
  • Mock documents are never cached.
  • VERSION_GATE_ENABLED=false disables the whole system.
  • Both are ignored in release builds.

Not done yet​

  • Handling the 426 on the client.
  • The API call.

Future ideas​

  • Tracking and a message while a trip is running.
  • A maintenance alert. This could be used for the case where something has broken and we do not want the user to see it.
  • An indicator on the profile page showing that a newer version is available.