A hull that leaves
My setup for web apps assumes a machine that is always on, holding the running thing. A phone app breaks that assumption completely. The app runs on a phone in someone else's pocket, and there is no berth of mine to keep it at. Here is the local Android dev setup I ended up with instead, walked end to end. What runs on the laptop, what the emulator is for, where the build happens, and how a saved file reaches a running app a second later.
I have written up the home dockyard before: a laptop to work on, an always-on old laptop to run things on, a copy of every project’s state somewhere off the box, one private network joining it all. The shape of that setup rests on one idea. The thing you built has to live somewhere, and that somewhere is a machine you keep running.
Then a project needed to be a phone app, and I spent the first hour looking for the machine it lived on. There isn’t one. A phone app does not sit in your harbour waiting for visitors. You build it, you load it aboard a device, and it sails off in someone’s pocket and keeps working whether your laptop is open or shut or at the bottom of a lake. Every instinct from the web setup pointed the wrong way.
So here is the shape I arrived at for building an Android app at home. The stack is React NativeA framework for building phone apps in React (the same JavaScript most web apps use), where the buttons and lists are real native controls rather than a web page in a wrapper. One codebase runs on Android and iOS. with ExpoA toolkit around React Native that handles the fiddly native build setup for you: a command-line tool, a bundle of common device features, and optional cloud services for builds and updates. Free to use; the cloud services have a free tier., which is the common way in and the one this note assumes. Words with a dotted underline have a plain explanation on hover, tap or focus.
1 · The app sails without you
The compiled app is the app. Once it is built and installed on a phone, it runs there, on that phone’s own processor, against that phone’s own storage. It works on a plane. Nothing of mine has to be reachable for it to open.
That is the whole reframe, and it takes a while to trust. With a web app the always-on box is the point. Close your laptop and the site stays up, because the site was never on your laptop to begin with. With a phone app there is no equivalent box, because the app is not running on a box at all. What you keep at home is a workshop: the place the drawing happens and the hull gets fitted out. The hull itself leaves.
2 · The dry dock
You still need somewhere to run the app while you build it, and there are two.
The emulatorA full phone, running as a window on your computer. It boots a real Android system, installs and runs your app, and is enough for most day-to-day work. A physical phone is still better for anything using real sensors. is a whole phone running as a window on
your computer. It boots a real Android system, installs your app, and is enough
for most of the day. A real phone plugged in over USB with developer mode on is
the other, and it is the one that matters for anything touching real hardware:
the camera, the GPS, the motion sensors, the health data. It is also the only
way to feel what a tester will feel. I keep both to hand and reach for the
emulator by default. adbAndroid Debug Bridge. The command-line tool that talks to a phone or emulator: list attached devices, install an app, read the logs. `adb devices` is the first thing you run when something is not connecting. is the tool that talks to
whichever is connected, and adb devices is the first thing you run when
nothing is showing up.
The machine that runs the emulator and does the builds needs a small kit
installed: a JDKJava Development Kit. Android’s build system runs on Java, so the machine that builds the app needs a specific JDK version installed even though you never write any Java yourself. of the right version, the
Android SDKThe Android build tools and platform files. You install a small set of it on whichever machine compiles the app: the platform tools, one Android version, and an emulator image. command-line tools, and one Android
system image. The gotcha that costs an afternoon is the emulator’s speed. On
Linux it needs KVMThe Linux feature that lets the emulator use the real processor instead of pretending to be one in software. Without it the emulator is unusably slow. You need the `/dev/kvm` device and your user in the `kvm` group. to borrow the real processor, which
means the /dev/kvm device has to exist and your user has to be in the kvm
group. Miss that and the emulator still starts, it just runs like it is wading
through treacle, and you will assume you broke something.
3 · A lifeboat you have to fit out
The easy way to run your JavaScript is Expo GoA free app from the store that runs your JavaScript without you building anything. It only carries the native features Expo ships with it, so the first time you need one it does not have, you have to build your own version instead.: a free app from the store, you point it at your project, your code loads, no build step anywhere. It works right up until you need a feature it was not built with.
Expo Go carries a fixed kit of native features. The moment your app needs a native moduleA piece of an app written in the phone platform’s own language (Kotlin/Java on Android, Swift on iOS) because it touches hardware or an OS feature JavaScript cannot reach directly, like the camera, sensors or the health store. that is not in that kit, and anything reading the phone’s own hardware is one, Expo Go simply cannot run it. What you do instead is build a dev clientA build of the app that includes your native dependencies plus the machinery to load JavaScript live from Metro. You build it once (again only when the native parts change) and then develop against it the same easy way as Expo Go.: the same easy live-reloading sandbox, except fitted out with your native gear. You build it once, install it, and only build it again when the native parts of the project change, which is rarely. Day to day it behaves exactly like Expo Go.
4 · Loading the hold
Building that dev client, and later the real thing you hand to people, is the slow job. It packs the JavaScript, the native code and every asset into one APKThe installable Android app file. Building one packs the JavaScript, the native code and the assets into a single file a phone can install directly.. There are two places it can happen.
On your own machine: GradleAndroid’s build system. It takes the source and produces the installable app file. Slow the first run, cached after that; you invoke it through the Expo command rather than directly., invoked through the Expo command, a few minutes after the first slow run, and free forever. This needs the Android SDK sitting on the machine, which is the one real install cost of the whole setup. Or in the cloud, on EASExpo Application Services: Expo’s hosted build, update and store-submission service. A free tier covers a small number of cloud builds a month and enough over-the-air updates for a test group. Optional; you can build entirely on your own machine., whose free tier covers about fifteen Android builds a month and needs nothing installed locally at all. Fifteen is plenty, because you are not rebuilding often. A build is loading the hold for a voyage, not something you do between edits.
The tooling itself is free. Local builds, the emulator, the bundler and the live reload cost nothing, and there is no charge for awesomeness; the only line item with a price tag is the cloud build service, which you can ignore entirely if you have the SDK installed.
5 · The tender run
With the dev client installed, the everyday loop has nothing slow in it. You run the Expo command and it starts MetroThe bundler that runs on your machine while you work. It watches your JavaScript files, packages them up, and serves that bundle to the app running on an emulator or phone, so a save shows up in the app a second later. on your laptop. Metro watches your files, and every time you save it repackages the JavaScript and hands the fresh bundle to whatever is running the dev client, over the local network. The emulator and a real phone on the same wifi can both be attached at once, and both update a second or so after the save. The coding agentAn AI you give a task and some tools, and it edits files, runs commands and iterates on its own until the task is done. Claude Code and Codex are the two I use. edits a file, you watch the screen redraw. It is the tightest loop in the whole stack, and it never touches Gradle.
6 · Resupply at sea
At some point someone who is not you needs the app on their phone. The first delivery is a single APK: build it, send a link, they allow installs from that source once, and it is on. That is a sideloadInstalling an app straight from a file rather than through a store. On Android you send someone the app file or a link, they allow installs from that source once, and it installs., and it is fine for one person or a handful.
After that first install, JavaScript-only changes go out over-the-air updateShipping a JavaScript-only change to an already-installed app without a new install. You publish the bundle to an update service; the app checks for it on launch and swaps it in. Changes to the native parts still need a fresh install.. You publish the new bundle to an update service, the app checks for it on launch and swaps it in, and your machine is not in that path at all. Twenty small fixes in an afternoon reach the tester without twenty reinstalls. When the group grows, the platform’s internal testing trackA store feature for handing pre-release builds to a small named list of testers with no review delay and proper auto-updates. On Android it costs a one-time developer-account fee, not a yearly one. takes over: a one-time developer-account fee on Android, up to a hundred testers, no review wait, real auto-updates. Native changes are the exception the whole way through. Those need a fresh APK, because you have changed what is in the hold, not just the cargo on deck.
7 · The chart room does not change
Everything above is new. The layer over the top is not. bosun-x still holds the project’s spec, its task list and the handoffA short written record of what was just done, what state things are in, and the next concrete step — so the next work session (a fresh agent, or you next week) picks up without re-deriving everything. note that lets the next session start without re-deriving a month of decisions, and it does not care in the slightest that this hull is a phone app instead of a web service. A dozen agent sessions on a mobile project scatter exactly like a dozen on anything else, and the fix is the same folder of plain files.
The one honest difference is the running half. bosun-x’s dashboard reads live container state to tell you what is up right now, and a phone app has no container on an always-on box to read. So that panel is quiet for this project. The spec, the tasks and the handoff carry the weight instead, which is where the real value was anyway.
8 · The whole yard
Put it together and it is one workshop and a set of lines running out of it. Your laptop holds the editor, the agent, the bundler and the SDK. The dry dock is an emulator and, when it matters, a real phone. A build turns the source into one APK, on your machine or in the cloud, and it happens rarely. The bundler carries every edit out to whatever is running, in about a second. Testers get one APK and then a stream of over-the-air updates. bosun-x sits across the top holding the spec and the log, same as for anything else. And the app, once it is built, runs on a phone and needs nothing of yours to be switched on.
You do not stand all of this up on day one. A laptop, an agent, the SDK and an emulator gets you moving. You add a real phone the first time the emulator lies to you about how something feels. You reach for the cloud build the day the SDK install gets under your skin, and the tester track when a link and a sideload stop scaling. Every piece earns its place the same way it does in the web dockyard, by the specific ache it takes away.
What took me longest was none of the setup. It was dropping the web habit of asking where the thing runs, and getting comfortable with the answer being a phone I will never see, somewhere, doing fine without me.
-x