Two ways to put a PWA on Android
| Bundled APK (this site) | Trusted Web Activity (Bubblewrap) | |
|---|---|---|
| Where the files live | Inside the APK | On your web server |
| Offline | Always, from first launch | Only what the service worker cached |
| Engine | Android System WebView | The user's Chrome |
| Needs a live HTTPS site + Digital Asset Links | No | Yes |
| Updating content | New build, new version | Deploy the website |
| Web Push, Web Share, payment handlers | No (native push on Pro) | Yes, as in Chrome |
A TWA is a window onto your hosted site; a bundled APK is a self-contained copy of it. Choose the bundle when the app must work with no connection, when there is no public site, or when you want no dependency on a server staying up.
What carries over unchanged
- All pages, styles, scripts, images and fonts — served from a real https origin, so modules and
fetch()work. - localStorage and IndexedDB, which persist across app launches and updates.
- Your app-shell UI and client-side routing (in hash mode — see SPA routing).
- Calls to your backend, when online — with the CORS origin allowed.
What stops, and what replaces it
| PWA feature | In the APK | Instead |
|---|---|---|
manifest.json name, icons, theme colour | Ignored | App name, icon and theme colour in the builder |
display: standalone / fullscreen | Ignored | The app is always full-screen; status bar colour from the theme |
| Splash from manifest | Ignored | Splash screen step (Pro) |
| Service worker | Registration fails | Nothing needed — files are local. Guard the call |
beforeinstallprompt / install button | Never fires | Hide the install UI in the app |
Web Push, Notification | Unavailable | Native push notifications (Pro) |
| Web Share, share target | Unavailable | Copy link, or share URLs (wa.me, mailto:) |
| App shortcuts, badging | Unavailable | — |
Detecting that you are inside the app
matchMedia('(display-mode: standalone)') is false in a WebView, and the user-agent string is not a reliable signal. The origin is:
const inApp = location.hostname === 'appassets.androidplatform.net'
if (inApp) {
document.documentElement.classList.add('in-app') // hide install banners, etc.
} else if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('./sw.js')
}
With the class in place, CSS like .in-app .install-banner { display: none } removes web-only UI without a second build.
A PWA with a server-rendered shell
If your PWA's HTML is generated per request by a server, there is no static index.html to bundle. Either produce a static build (most frameworks can), or build a website app from your URL instead — the builder's URL option wraps the live site, with an offline screen when there is no connection.