by GenericJam
Tags: mob, mobile, cross, platform, native, ios, android
The hero’s journey
I’ve lived many lives. I used to build houses. I used to work on an oil rig. Perhaps most shamefully I used to be a mobile dev. I longed to join the cool kids at the backend table. I especially wanted to work in Erlang but no one would give me a shot. Frontend to Erlang? You must be joking! Mobile to Erlang? Even worse!
So I wandered the tundra. The trackless thankless steppe. I learned all the frameworks I could and kept my ear to the ground. Waiting for the Erlang opportunity to arise. Then I would pounce. Flask… Erlang? React… Erlang? React Native… well I guess I should collect a paycheque. So I settled into React Native for a few years. I also got a taste of native iOS along the way.
Then I landed a job at a company that had an extensive React Native app and they wanted to rebuild their backend. They were considering Elixir. I looked into it and Elixir was built on top of Erlang. Now was my time to pounce! Since that time I’ve never looked back. I spurned my past shame as a frontend dev, nay a mobile dev.
As a React Native developer clearly half your job is keeping up with the constant flow of changes coming at you. You have changes from the OS platforms, JavaScript with its innumerable dependencies, React, React Native and the unholy bodgery of the glue between React Native and the individual platforms it precariously rides on like a monkey on a tiger. Dependabot is really pulling its weight there. With React Native in any long running app the maintenance burden is very high and the platform code you still have to write for native integrations can be extensive and your gradle and your plists can be quite tortured due to filling them with magic incantations cited by joey96 on some github issue. Pick the one with the most :tada:s and hope for the best.

So when I fled the frontend it was for good reason. I needed the sanity of stuff that just made sense. I remember the joy of learning Elixir and it just made so much sense. Every decision was well thought out. It followed Erlang’s runtime, which I already understood, and combined it with a glorious syntax and cushy devux. It was calling to me. I knew I was home and I knew I wanted to stay.

Unrequited love
But I still had my eye on mobile. We need to spread out to other platforms. Javascript long since jumped the browser’s petri dish and was infecting the world. Now was the time to reverse colonize those spaces. Every time I had to write code for the browser or mobile I wanted the BEAM and Elixir to be there too. I wanted the BEAM on mobile but the time was not right. The first project to bundle Elixir and the BEAM on mobile was elixir-desktop. This was fantastic! Progress! But I heard the launch times were quite long so I didn’t think we had arrived at the final formulation. Then LiveView Native came along. I remember listening to the talk at one of the Elixir conferences and being so happy that we were getting closer to the goal. I also strongly resonated with the vision of building to the native view tree directly, extending the LiveView idea of patching the DOM to mobile. Up until then I didn’t realize that was actually possible so that opened some doors in my brain. I hoped that elixir-desktop would be able to merge or that users would be able to combine with LVN but alas it was not meant to be. LVN was dropped.
Just me and my bestie till the tokens run out
Last year I started using Claude for side projects and I immediately saw its value. Not just for coding. It did a great job of reverse engineering and coding and mulling ideas over and its capabilities grew. At some point I realized that moonshots were no longer out of reach. So I played around with various side projects as you can see in previous blog entries. I figured out how to leverage Claude pretty well.
Then the stars aligned. I was laid off from my job which was great timing as I’m trying to relocate anyway. Then I got nerd sniped by a friend asking about mobile app stuff and asking how to do notifications on a PWA. I told him what I knew but then I wanted to make a prototype to verify that I knew anything. I built it with Claude and sent it to him. Then that sent my mind back into the mobile app space. I was dancing a little too close to the edge of the rabbit hole and fell in. I thought I wonder if I could build more or less what LVN was trying to build by going through NativeScript. So I built another prototype and it worked. Then I thought, “I wonder if I just took out all the layers between and hooked the BEAM directly to the native UI if that would work”… and it did. This was the eureka moment when Mob was conceived.
In one sentence:
Mob is LiveView for native mobile via NIF with BEAM on device with native views and all logic in Elixir.
NIFs are your friends
In my previous projects I had been playing around with Pythonx and other NIFs (Native Implemented Function - roughly equivalent to FFI) so I was no longer scared off by the concept. One of the main caveats with NIFs is they can crash the BEAM. In the context of mobile that just means your app crashes which gets turned into a bug report so ultimately pretty low stakes. Claude knows how to write all the languages to build the bridge. It just needs guidance, taste and a steady hand on the tiller as it lacks persistence. I’ve already been thinking about this problem for years. Many one hit wonders had one good album because they had been ruminating on it forever. Mob is my one good album.
There is a lot of prior art and good and bad decisions so like Elixir that could see all the mistakes that all the other languages made and just adopt the right answer I’m in a similar privileged position.
Solving solved problems
For the most part, the right decisions were already made by Phoenix and Liveview and I leaned into those decisions but I still want it to feel like mobile and we are after all trying to directly address the native view trees which are not html. So the interactions happen by send / receive style event which works great and in some cases the alternative isn’t even possible because we are tied in as a NIF so we can’t use callbacks like Flutter or RN do. This is totally fine and the default use pattern on the BEAM so all is well and Elixir and BEAM devs will feel at home with this event model.
There were a handful of places where Phoenix doesn’t map to mobile, for example the navigation. Mobile navigation is usually stack based, not route based. So the navigation uses stack / screen based navigation. By default it implements a stack in a GenServer, which you can change if you want. If you want your navigation to go two deep on a particular page and back takes you back twice by default you can do that but the default behaviour you’d have to go back twice. We are handling the navigation logic on the Elixir side but you can still play a navigation animation like swipe right or left programmatically so everything will be indistinguishable from native interactions. You can even play the opposite animation if you want to be a weirdo.
Years ago when I started thinking about this it really wasn’t clear to me how the layout would work. I assumed I’d have to build some sort of rendering engine myself. Then Scenic came out and other packagers and bundlers came out. I thought maybe we could plug into an existing framework like RN or Flutter but none of those felt like the answer. When LVN came out with the idea or writing to the native view trees directly I knew this was the way to go. LVN also pioneered the three letter sigil which makes ~MOB possible. The layouts looked like heex for a mobile view tree which is exactly what I wanted. So that question was basically settled although I have explored other options since.
Sigils or data layouts: you decide! … or just leave it up to Claude
I was partnering with Claude on this and Claude likes data (map) and doesn’t care about sigils. It put in data as the first iteration and after some interaction with users I decided to keep the data layer exposed. It’s a cleaner interaction if that’s how your mind works. I think most programmers migrating from LiveView will want the sigils. The data layer is a bit more composable but sigils resolve to data so they are effectively the same thing. I am also building this so Claude can just build with no one ever looking at the code, in which case Claude and cousins will likely just skip the sigils altogether.
Mobile UX != Web UX
From my experience developing on the native side there are ux flows that don’t viably work in a browser that actually feel quite nice on mobile. It’s important to know how big the screen is. Being able to know where the average user’s thumb will land when comfortably holding the phone is important information to consider on mobile. The bar has to be higher than just ‘does the screen fail gracefully to a smaller size’. Native designers have thought a lot about this so don’t throw away their work. Lean into it.
With native there is the carrot and the stick approach. On one hand you get to ride in on the coat tails of all the ux work they’ve done and the system really incentivises you and sometimes you have to. Part of that get to is now we just tap into native levels of UI performance and we did very little work to get here. There are also things you have to do like be a good citizen on the device or you might get punted from the store. You have to be aware of the shape of the screen. For example iOS has an island for the camera right now. It used to have a notch. If you ignore that your app will look amateurish. So all of that has to be exposed to the developer and taken into consideration.

Mobile is not web. One of the experiences that shaped my opinion here is actually developing in Xcode. Most people have a very strong love / hate relationship with Xcode, leaning more to the hate. I count myself there. However, one of the cool things about Xcode was that you can put constraints on the elements on the screen. It doesn’t default to overflow and a scrollbar, unlike React Native that acts like a web page by default. So many people are used to this type of interface we often just assume that’s the right mental model. Mobile designers have spent a lot of time thinking about what type of interactions will work best on their platform and the platforms prioritize these interactions with performance enhancements, etc. So just use the blessed path and you get everything the mobile platform wants for itself and its ecosystem thrown in for free.
They figured out UX but not dev UX
There is less thought put into the programming side as you might like, especially from a runtime perspective. Mobile is inherently event based and the native languages on mobile all inherit the C runtime model, sometimes with callbacks thrown in for ‘real time’ interactions. Callbacks have never been overly intuitive for me, especially when you have to use them. So Elixir and the BEAM provide a much nicer runtime model where events happen and you respond to events. It’s a very straightforward runtime mental model which is easy to reason about.
Everything possible on a native platform should be possible in Mob, even if we haven’t explicitly built it. If a platform unlocks something tomorrow or a manufacturer forks Android and puts something else in, our devs should be able to use it. That’s the goal.
Full native performance in the UI and full BEAM abilities on the logic side. The NIF bridge is a pretty clean separation so ideally Elixir devs should be able to fully control the UI from Elixir. That being said, a happy accident is that you can still write native code on the native side. This will be covered more in the next post.
I want to give the full power of the BEAM. This means distribution. This means BEAM tuning flags. This means hot code upgrades. I want to expose everything and let the dev decide what is right for them. It’s possible the app stores will not look favourably on hot upgrades but I still want to make that a developer choice, not cripple the framework to accommodate corporate policy.
As a basic design principle I want to expose the whole range of possibilities while providing sensible defaults if the dev does the default action. Like the rest of Elixir does, I want to hand you the keys and do what you want to do.
Painless onboarding
Another thing I’m stealing from Phoenix and Elixir in general (Ash deserves mention as well) is the seamless onboarding process. I’ve spent much of my time so far on trying to make sure that a project will build and run first time, every time on every device we say we support. Right now that support is mostly Mac because anyone doing cross platform development pretty much needs to be on Mac due to needing a Mac to sign iOS apps. That being said, onboarding on Linux also works but you won’t be able to build iOS apps there.
When I was initially thinking of this I thought it would be a package you’d install and have everything living in mob package but as my thinking evolved I realized there needed to be more support repos. So now there is mob_new which is now the only thing you actually install and the rest is installed with command line args similar to phx_new. We also need a dev server to run the project, hot code reload as well as debugging so that lives in mob_dev.
I want the onboarding and debugging experience to live up to the already high standard set by Elixir, Phoenix and the BEAM so I built a debug server which still needs a bit of polish but does work. You can run mix mob.server to run it in the browser and view system logs and Elixir logs from both platforms.
Agents: all is forgiven for the IP theft if we get the bag
Support for AI coding is already excellent due to being able to tap into iex and BEAM distribution. This combined with adb and xcrun mcp tools means you already have pretty good access to a running app.
But it could be even better and the path to get there is not that long. We’ll cover that in the next post.
Uniquely positioned for global domination
Mob already holds a unique position in that it is a single language directly addressing native platforms.
It is the only language that can give the level of introspection needed for superior testing frameworks and agentic coding.
This is part of how Elixir becomes a true full stack language.
Mob
