Field notes · Building Mapeak
Putting Mapeak on the Dashboard
Users had been asking for Android Auto, but I’d always priced it as too much work for too little return. AI is what finally made me try — and also what showed me exactly where its help runs out.
There was real demand for Android Auto. A number of Mapeak users had asked for it over time — not a flood, but enough that I never doubted it was worth doing eventually. What kept it on the someday list for so long wasn’t a lack of interest. It was the math. Every time I looked at it, it read as a huge task for not much return: a big, native, platform-specific build effort bolted onto an app that mostly lives in a web layer (it uses Capacitor), all to serve a feature a fraction of users would ever touch. High cost, modest payoff. So I kept deferring it.
Two things didn’t help the estimate. Android’s documentation is famously rough, and the Car App Library is a particularly thin, example-starved corner of it — which made the “huge task” feel even huger. And I had no good way to test, no car with Android Auto to actually run the thing in and see whether any of it worked. Both of those pushed the effort up and the confidence down.
Recently the calculation seemed to flip. I got a car with Android Auto, which finally made testing possible besides the Android Auto Emulator. And in the time since I’d last looked, AI assistants had gotten genuinely good — good enough, I figured, to collapse all that effort and carry me through the documentation that had made the task look so big. Suddenly it didn’t read as high-cost-low-return anymore, so I finally took it on.
The reality was more mixed than the pitch in my head. AI did help in places — it explained corners of the Car App Library the docs barely mention, and gave me scaffolding to build on — but it was a useful assistant, not the effort-collapsing engine I’d imagined. And early on it actively cost me: I leaned on it too hard at the start, and what it confidently handed back sent me down the wrong path before I caught it. In the end, two things cost me real time: the false starts AI led me into early on, and the stretch it took to land on the right way to think about the problem. That second one is the part worth writing about — not because it was some exotic new paradigm, but because the answer turned out to be an architecture I already relied on in Mapeak, once I recognized it belonged here too. Here’s the whole detour, mistakes included.
Starting the way everyone starts now: asking the AI to do it
My first move was to hand the problem to an AI assistant and ask it to wire up Android Auto for me. I described the app, pointed at the relevant pieces, and asked for an implementation.
What I got back was confident, plausible, and wrong. It produced code that looked like Android Auto code — the right class names, the right imports, the general shape of a navigation app — but it didn’t account for how Mapeak is actually built or how the Android Auto lifecycle really works. It was pattern-matching against generic tutorials, and Mapeak is not a generic app. The result compiled in spirit and failed in practice.
This isn’t a complaint about AI. It’s a useful data point: when a problem depends on the specific architecture of your app and the specific constraints of a platform, generic help can only take you to the trailhead. I had to actually understand the terrain myself.
First, somewhere to run it
Before any code mattered, I needed a way to run Android Auto while developing. Google’s Desktop Head Unit (DHU) turns your workstation into a simulated car head unit — but it doesn’t run Android Auto itself, it only renders the display. The app still has to run on a phone over USB, and you can’t install Android Auto on a phone emulator. So a physical phone, on a recent-enough Android version with the Android Auto app and developer mode enabled, is non-negotiable.
Once you have the phone, the desk setup is quick:
- In the Android Auto app on the phone, enable developer mode (tap the About header repeatedly), then start the head-unit server.
- Plug the phone in over USB with debugging on, and forward the DHU port:
adb forward tcp:5277 tcp:5277- Launch the DHU binary from your SDK:
/Library/Android/sdk/extras/google/auto/desktop-head-unitThe map then shows up on your monitor, the same as it would in the car.
The example that unblocked me
So I went looking for something concrete and trustworthy. With the help of Bart Louwers, Maplibre’s native maintainer, I came across Ahmed El-Helw’s write-up on running MapLibre on Android Auto, which did the most important thing an article can do at that stage: it convinced me this was actually possible, and walked through the different ways to get a MapLibre map drawing on the car’s surface.
Bart also pointed me at the MapLibre Android Auto sample in the MapLibre org on GitHub and this his pull request from Ahemd. The sample really clicked once I leaned on it, which added the cleaner approach — the one that turned out to be the right code for what I needed. With that in place, I got an actual map drawing on the car screen — real tiles, real movement, the thing I wanted to see.
Get Harel Mazor’s stories in your inbox
Join Medium for free to get updates from this writer.
That early win was motivating, but it also set a trap. Because the map showed up so quickly, I assumed the rest of the job was small: just feed the car screen the same data the phone already has.
My wrong assumption: “it’s just another screen”
The mental model I’d built was that Android Auto is a second display for Mapeak. The phone has the app; the car has a mirror of part of it. Under that model, the work is plumbing — take the state I already have and push the relevant slice of it to the second screen.
That model is wrong, and it’s wrong in a way that matters specifically because of how Mapeak is built.
Mapeak runs on Capacitor. The bulk of the app — the logic, the state, the UI — lives in the web layer. Android Auto, on the other hand, runs as native Android through the Car App Library, in its own context. There is no shared web view sitting between them, no convenient shared store, almost no code and no state that the two sides naturally have in common. The “second screen” framing assumes a single running app with two outputs. That’s not the situation.
Moreover, when I tested the code I had the phone with the app open alongside the DHU, and I was focused on syncing both “screens” so that when you change the position in one screen, it is reflected in the other. But the truth is, when you plugin the phone in the car, you usually turn its screen off.
The reframe: it’s another app
The shift that made everything click was to stop thinking of Android Auto as a screen and start thinking of it as a separate app that happens to be about the same thing.
The detail that forced this reframe was the lifecycle. Android Auto needs to run when the phone is locked, and more generally when the phone is plugged into the car. In that moment, the main Mapeak app might not be running at all. There may be nothing to “sync from.” The car experience can be summoned by the system, cold, with the phone’s screen dark, while the rich web-layer app that I’d been imagining as the source of truth simply isn’t alive.
Android Auto is not a screen. It’s an app.
Once I accepted that, the architecture question changed completely. The question was no longer “how do I mirror the app onto the car?” It became “how does this separate, independently-launchable thing get the information it needs to be useful, even when its sibling app is asleep?”
Giving the car its own state
The answer was to stop relying on a live connection between the two and give the car experience a durable view of the world.
Here’s the thing: I didn’t have to invent anything for this. Mapeak already keeps its source of truth in a small, redux-style store (using NGXS) — a single authoritative place that holds the real state, with everything else derived from it. That pattern had already served me well on the Capacitor side, where keeping one store as the truth makes state predictable and decouples it from any particular piece of UI. The realization wasn’t a new architecture; it was seeing that Android Auto is just one more consumer of that same truth.
So the main app writes the part of its state that matters for driving into the store, and the Android Auto side reads from it. The store persists between Android Auto initializations, so when the system spins the car experience up — locked screen, app not running, whatever — there’s already a coherent snapshot waiting for it instead of an empty void.
This is the part that finally felt right. Instead of streaming state from a running app to a passive screen, I was syncing a slice of the app state into a separate Android Auto state, with a persistent store, that raises events when the data changes, as the handoff point between two things that may never be awake at the same time. The car app no longer depended on the phone app being there. It just depended on the last thing the phone app had written down. It also gave me the possibility to store the last location of the user and zoom level which might not be the same as the one in the app.
The unglamorous final mile: screens are weird
With the architecture sorted, the map rendering correctly, and state flowing reliably, I hit the class of problems nobody warns you about: car screens are nothing like phone screens, and they’re not even consistent with each other.
First, aspect ratio and DPI on large displays. A big head unit is not just a bigger phone. The aspect ratios are unusual and the pixel density is different, and a map that looked crisp and correctly proportioned on a handset came too small on a wide dashboard display. I had to handle ratio and DPI explicitly rather than assuming the phone’s values carried over.
Second, and more subtly, the visible area isn’t the whole surface. Android Auto can hand you a drawing surface where part of it is covered — system UI, navigation strips, or another app occupying a region of the screen. If you naively center the user’s position in the middle of the full surface, the blue dot can end up hidden behind whatever is overlapping. So I had to account for the actually-visible region and place the current user position within the remaining space, so the most important thing on the map — where you are — stays where the driver can see it.
A map from the 1940s, on a 2020s dashboard
There was one more thing to verify: the raster basemap path. To exercise it I needed a raster source, and almost on a whim I reached for an old one — a map from the 1940s. It was just a convenient set of tiles to throw at the renderer.
But when it came up on the car screen, it stopped being a test. An old map was glowing on a touchscreen wired into a modern car’s computer. It was a genuine collision of past and future — decades-old cartography piped through the exact pipeline I’d just spent weeks building, sitting in a dashboard that the mapmakers could never have imagined. I’d been so deep in lifecycle diagrams and DPI math that I’d forgotten the thing I was actually building: a way to put any map, from any time, in front of someone in a car.






