iPhone app · dominoes scorekeeper
Anota
An iPhone scorekeeper for Dominican-style two-team dominoes, so nobody keeps score on paper.
- Role
- Timeline
- Stack
- Links
By the numbers
5
timed steps in the ad startup chain
3
layout tiers, picked from the usable screen height
24
unit tests in 5 files
2
one-time purchases: Remove Ads and Anota Pro
- 1 · limit 8 s Foreground Waits for the app to be active. Then it goes on anyway.
- 2 · limit 15 s UMP consent Runs the UMP consent flow. An error leaves the decision to step 4.
- 3 · limit 5 s ATT status Asks for tracking permission only if iOS has no answer yet. An error means non-personalized ads.
- 4 · limit 5 s Consent info Reads whether ads may be requested. A clear no means no ads.
- 5 · limit 15 s SDK init Starts the Google Mobile Ads SDK. An error means no ads.
Result
- Ready The banner slot shows an ad.
- Not ready No ads. The app works the same.
The scoreboard never waits for this chain: it runs in the background after launch, and a Remove Ads or Anota Pro purchase skips it.
The idea
Anota is an iPhone scorekeeper for Dominican-style two-team dominoes. Both teams, the round history and the controls fit on one screen, so nobody keeps score on paper.
What I built
The app is built with Expo and React Native in strict TypeScript. Match logic is a pure reducer: after every change, a settle step recomputes the winner. A saved match is checked field by field before it loads, so a corrupt save cannot crash the app. Spanish and English share one dictionary type.
Anota is free with a banner ad and has two one-time purchases: Remove Ads at $1.99, and Anota Pro at $2.99, which removes ads and unlocks every theme. Ads start only after the UMP consent and App Tracking Transparency steps. The winner screen renders a 1080 px score card and opens the share sheet.
The hard parts
-
An ad startup that cannot block the scoreboard
The ad SDK starts through five steps: wait for the app to be in the foreground, UMP consent, the App Tracking Transparency status, consent info, then SDK init. Each step has its own timeout, so a stalled network or a stuck app state falls back to non-personalized ads or no ads instead of hanging.
-
Purchases that report the right failure
The purchase layer registers its listeners before it opens the store connection and puts a timeout on every store call. It ignores store errors that arrive when no purchase is in progress, and it reports a missing product separately from a failed payment.
-
One screen on every iPhone
The scoreboard never scrolls. The app picks one of three size tiers from the usable height, after subtracting the banner ad, so the layout still fits an iPhone SE while an ad is showing.
What’s next
Prepare the Android version.
Try it
Related by idea
Rules in a module with no UI imports
English and Spanish with key parity enforced