Cross-platform mobile apps โ services โ Ahsan Mahmood
One React codebase shipped to the web and to Android through Capacitor.
One React and TypeScript codebase becoming a web app and an Android app, rather than two teams building the same product twice.
Features land on the web first.
The Android build catches up later, or it does not; within a year the two products disagree about what a user account is and which of them is right.
Divergence is rarely dramatic. It shows up as a field the web form validates and the mobile form does not, or a permission the Android build asks for and the web build has never heard of.
Each is small. Together they are the reason a bug report has to be answered with a question about which of the two apps the person was actually holding at the time.
The usual answer is to build the thing twice: a web team and an Android team, two codebases and two release cycles. For a small company that is not a staffing problem. It is a maintenance bill that arrives every time anything changes, and it arrives twice.
No iOS.
That limit belongs at the top of this page rather than the bottom. There is no Apple Developer account here, so nothing I build has gone to the App Store; a portfolio that claims a platform it has not shipped to is the first thing a client finds out about.
Android and the web are the surfaces I can take through to release. It is a shorter list than some, and it is the list I can stand behind.
The second constraint shapes the code rather than the platform list. One codebase, one person, and no server kept alive behind it. My name is Ahsan Mahmood, and the person who writes the web screen is the person who packages the Android build and answers the store reviewer.
Capacitor is what makes that work. The React and TypeScript that draws the web app is packaged as an Android application, and native capability arrives as a plugin rather than as a second product.
What is included does not change from project to project.
One codebase, web plus Android. Native capability through Capacitor plugins. Offline behaviour and reconciliation, so a form filled on a train is still there when the signal returns.
Play Store submission material, including the permission justifications and the answers a reviewer asks for. And over-the-air updates, so a fix reaches installed devices without a store round trip.
HabitForge, PregnancyPal, 2FA Studio, CallVault and SMS Mobile App are mobile applications of this shape, and each has its own page on this site.
Offline is the part that gets skipped and then rebuilt. A phone loses signal in a lift, on a train, in a basement car park, and the honest question is what the app does with the half-finished thing in front of the user. The write is queued and reconciled when the connection returns, rather than discarded with an apology.
A queued write needs a rule for what happens when it lands late and the row has moved underneath it. That rule is a product decision rather than a technical one, so it gets made on the call and written down.
Every native branch goes at the call site rather than inside a helper that quietly falls back, and every native plugin sits behind a dynamic import so the web bundle carries none of it. A helper that degrades silently is a defect you meet on a device, in a week when nobody is looking for one.
The cost of one codebase is that every native capability is a second path through the same feature. A camera permission behaves differently on a phone than in a browser tab, and the version that works on the web is not evidence about the one on the device. The device is the only witness.
So the screen gets walked twice.
The second cost is the store. A submission is a queue somebody else owns. The permission set a reviewer reads is the one in the merged manifest rather than the one you wrote, and a plugin can add a permission you never asked for.
Finding that at submission is expensive. Gradle also skips the merge task when it decides nothing has changed. It hands back the previous manifest, which reads exactly like a fresh one, so that file gets re-read with the task forced rather than trusted.
Over-the-air updates take the sting out of small fixes, because the web layer can be replaced without a release. Anything reaching into the native project still goes through the store. The rest waits.
Two limits, stated here rather than discovered later. There is no iOS build, and there will not be one until an Apple Developer account exists behind it. It is a hard limit. Visual design as a standalone deliverable is not offered either, and the third-party subscriptions the finished app needs to run are yours rather than mine.
Thirty days of bug fixes at no charge, and after that nothing is automatic.
The store's review queue is the one date on this page I cannot give you.
from $800
How long it takes
Small features 1โ2 weeks, an MVP 4โ8 weeks, a full production app 8โ16 weeks. Those are ranges I have hit on work of that shape before. The scoping call settles which native capabilities are in scope, because each one is a second path through the same feature and has to be walked on a device as well as in a browser.
Every engagement of this shape covers
- One codebase, web plus Android
- Native capability through Capacitor plugins
- Offline behaviour and reconciliation
- Play Store submission material
- Over-the-air updates without a store round trip
Built with
- Capacitor
- Android
- TypeScript
Does this include an iPhone app?
No. There is no Apple Developer account here, so nothing I build has shipped to the App Store and I do not take work that needs it. Android and the web are the surfaces I can take through to release. That limit is on the page rather than in a conversation on week three.
Is the Android app the same code as the website?
Yes. One React and TypeScript codebase becomes both, packaged for Android through Capacitor, so a feature is written once rather than twice. Native capability arrives as a Capacitor plugin behind a branch at the call site. The web bundle does not carry the native plugins at all.
Can you ship a fix without waiting for a store review?
Yes for the web layer, where most fixes live, because an over-the-air update replaces it on installed devices without a store round trip. That mechanism is part of the fixed scope rather than an extra. A change that reaches into the native project still needs a store release.
Do you handle the Play Store submission?
Yes, and the submission material is part of what every build of this shape covers: the listing copy, the permission justifications, and the answers a reviewer asks for. The permission set that matters is the one in the merged manifest, because a plugin can add a permission you never requested. Review timing belongs to the store rather than to me.
How much does a cross-platform build cost?
$800 USD is the floor for a small, well-defined feature. Everything above that comes from a scoping call, in writing, before any work starts. What moves the number here is how many native capabilities are in scope, because each one is a second path to test on hardware, and whether the Play submission and its permission justifications are mine to write.
What is not included, whichever service I pick?
Visual design as a standalone deliverable. Native iOS โ I have no Apple Developer account, so I will not claim a platform I cannot ship to. Blockchain work. Copywriting and content. Third-party subscriptions and domain costs are yours, and any architecture that needs a paid service in the critical path gets flagged before it is built, not after.
https://aoneahsan.com/services/cross-platform-mobile-apps