Faragiri

Learn MobileCode

Kwabena's app kept its place for three weeks on his desk and forgot it in everybody's pocket. That gap is the whole topic.

6Sections
6Min read
2Depth

The decision

Write it once or write it twice, and the device decides which

Three of the four rows go the same way for most apps. The fourth is the one that overturns the decision, and it is the one nobody checks first.

One shared codebaseBoth stores from one project, at close to half the cost
Two native codebasesWritten twice, in the platform's own language and tools
Lists, forms and textIdentical on both, written onceThe same screen built twice
Cost of the second storeA build targetA second team, or a second half of the year
A feature the platform shipped last autumnAvailable once somebody writes the bridgeAvailable the day the platform ships it
Background location and widgetsPossible, and the hard part is the bridgeThe documented path, with the platform's own examples

The rows are not weighted, and in practice the last two carry the decision on their own. An app that needs neither of them has no reason to be written twice.

Kwabena Mensah built a bus times app for Kumasi and tested it for three weeks on his own phone, where it never once lost its place. The first week it was public, the complaint that arrived over and over was that it forgot which stop you were watching. His phone was plugged in on a desk. Everybody else's was in a pocket, being interrupted.

That is the whole difference between this topic and the one next to it. A web page is asked for and delivered. An app is installed on a device that is short of power, short of attention and free to shut the app down without telling it, and every one of the five clusters here is a consequence of that.

The thing the desk never shows you

Both platforms treat a backgrounded app as a resource to be reclaimed. An app is killed in the background and must restore itself from what it saved. Not paused, not suspended in a way that survives: ended, with its memory taken back, and then relaunched later at whatever screen it claims it was on. If the app did not write down which stop was selected, that selection did not exist.

Kwabena's fix was eleven lines: write the selected stop to the device's small key value store the moment it changes, read it back on launch, and treat the in memory copy as a cache of that rather than as the truth. The rule generalises to every piece of state an app holds. Anything not written down is lost the first time somebody takes a phone call.

The reason this is invisible in testing is that a phone attached to a laptop with a debugger running is explicitly protected from the behaviour. The platform will not reclaim an app it is being asked to inspect, so the exact fault the device is designed to produce is the one the development setup suppresses.

Two numbers that decide a layout

A pointer on a desk is a single pixel and a finger is not. 44 points on iOS and 48 density independent pixels on Android is the floor for a tap target, and both numbers are the published guidance rather than anybody's taste. They are not pixel measurements. A point and a density independent pixel are units that stay the same physical size across screens of different sharpness, which is the whole reason they exist, and a layout specified in raw pixels is a layout that shrinks on exactly the phones that cost the most.

Kwabena's stop list had rows 32 points high because that is what looked right at the size his design tool was showing him. On a phone, one row in six was mis-tapped, and the people mis-tapping did not report a bug. They reported that the app was annoying, which is the form most of these faults arrive in and the reason thumbs, interruptions and a screen you hold is a cluster rather than a footnote.

The choice everybody makes first and should make third

The question that dominates the topic is whether to write once or twice. It is a real question with a real answer, and the answer depends on how much of the device the app actually touches. An app that shows lists, forms and text can be written once and shipped to both stores at very close to half the cost. An app built around the camera, background location, a widget on the home screen or anything the platform shipped last autumn cannot, because one codebase, two stores, and the newest platform feature arrives late: the bridge to it has to be written by somebody after the platform ships it.

Kwabena's app is lists and a map, so the shared answer in one codebase, both stores, and what it costs was correct for him. It would have been the wrong answer for an app that wakes up when you arrive at a bus stop, and the wrongness would not have been visible for four months.

One release

The build is the first of five gates and the only one made of code

The width of each band is Kwabena's own count for one release, so the drop is the data: the largest single loss is between installed and opened a second time.

  1. Builds on the machineThe gate every tutorial ends at, and the cheapest of the five100
  2. Passes reviewRejected once for a permission asked at the door, which cost four days71
  3. Found in the storeA listing is a search result before it is an app34
  4. InstalledWhere most measurement stops, and where nothing has happened yet19
  5. Opened a second timeThe first honest number, and the one the forgotten stop was destroying6

The counts are one small app's first release scaled to a hundred, not an industry figure. The shape is the claim: four of the five gates are passed after the code is finished.

The queue that is not code

Shipping an app is not deploying. There is a review, it is a queue, and the queue has a shape worth knowing before the first submission rather than after. A rejection puts you back at the end of the review queue, so the cost of a careless submission is measured in days rather than in the ten minutes it takes to fix whatever was wrong. The common rejections are not subtle: a missing privacy declaration, a login the reviewer cannot get through, or a permission the app asks for and never visibly uses.

The numbers for his first release, scaled to a hundred, say where a release is actually lost. Of 100 builds that ran on his own machine, 71 got through review, 34 were found by somebody searching the store, 19 were installed and 6 were opened a second time. Four of those five gates are passed after the code is finished, and the largest single drop is the last one.

Kwabena asked for location on first launch, before showing anything, and was rejected for it. The successful version asks the third time the user opens the app, at the moment they tap the button that needs it. That change raised the proportion of people who agreed to share location, which is the ordinary outcome: a permission requested with a visible reason in front of it is granted far more often than one requested at the door.

Where people stall

The stall is the second platform, and it is a money problem disguised as a technical one. Somebody ships on the larger half of the world's phones, gets users, and then discovers that the revenue is on the other one, in fewer devices, and the ones that spend, and that reaching it means a Mac, a paid developer account and a second set of platform conventions. That is not a thing you learn your way past in a weekend, and it is the point at which a side project either becomes a budget or stops.

What to open, in 2026

Xcode is the only way to build for iOS and it requires a Mac, which is a hardware decision made by a software tool and is worth knowing before starting rather than after. Android Studio builds for Android on any of the three desktop systems, is free, and replaced Eclipse with the Android plugin, which is what tutorials older than about 2015 still describe.

For the shared path, Flutter and React Native are the two serious options and they divide on what you already know: React Native is the answer if the team writes JavaScript, and Flutter is the answer if it does not and would rather learn one thing that behaves identically on both. Both replaced the earlier generation of tools that shipped a website inside an app shell, an approach that is still occasionally correct and that users can reliably feel.

What none of this settles is how the app pays for itself, which changes the design of the thing rather than just its billing, and is the argument in paid, free, subscription and advertising. It sits beside the same question asked of the browser in everything between a browser and a server.

Alongside this

Above this