DEV Community

Mark Barnett
Mark Barnett

Posted on

How I shipped my PWA to Google Play as a TWA (and what actually went wrong)

I built Money Me as a PWA. It works offline, it's installable, it feels like a native app. At some point I decided to put it on Google Play because people kept asking "is it in the store?"

The TWA route seemed obvious. Wrap the existing PWA in an Android shell, ship it. I figured a day of work, maybe two.

It took me a lot longer than that.

So what is a TWA

A Trusted Web Activity is basically an Android app that opens your website in Chrome, but with the browser UI stripped away. No address bar, no back button. Fullscreen. If you didn't know better you'd think it was a regular Android app.

The "trusted" bit: you prove you own the domain by putting a specific JSON file on your server. Android checks it against your app's signing key. If it matches, Chrome hides the browser chrome (pun maybe intended). If it doesn't match, you get an ugly blue bar at the top and your app looks like a glorified bookmark.

Important thing I didn't understand at first: a TWA is NOT a WebView. It's actual Chrome running your site. Service workers work, push notifications work, everything your PWA already does just... works. This is the main reason to use a TWA over something like Capacitor or a WebView wrapper.

Bubblewrap

Google has a tool called Bubblewrap that generates an Android project from your web manifest. Point it at your manifest URL, it spits out a project, you build an APK.

My advice: don't treat it as a black box. You will need to open the generated project and mess with build.gradle and the Android manifest at some point. If you've never seen an Android project before, spend 20 minutes understanding the structure first. You'll thank yourself later when something breaks and the error message means nothing to you.

The blue bar (aka my nemesis for two days)

This is where I lost the most time. The blue navigation bar shows up when Chrome can't verify you own the domain. Your app still works fine underneath, it just looks terrible.

The fix is a file at /.well-known/assetlinks.json:

[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.yourapp.twa",
    "sha256_cert_fingerprints": ["YOUR:SHA256:FINGERPRINT:HERE"]
  }
}]
Enter fullscreen mode Exit fullscreen mode

Simple enough. Except I got the fingerprint wrong three times before figuring out what was happening.

The problem: you have multiple signing keys and they all have different fingerprints. There's your local debug key, your upload key, and then Google Play's own app signing key. Play App Signing is mandatory for new apps now, which means Google re-signs your bundle after you upload it. So the fingerprint that matters for asset links is Google's key, not yours.

Go to Play Console → Setup → App signing → App signing key certificate and grab the SHA-256 from there. I wasted hours using my upload key fingerprint instead. The blue bar kept showing up and I kept double-checking the JSON format, the content type, the file path. The file was fine. The fingerprint was just wrong.

Other things that bit me:

Chrome caches the verification result. You fix the asset links file, reinstall the app, and... blue bar is still there. You need to clear Chrome's data on the test device. Not just the cache. The data. I was clearing the app cache only and wondering why nothing changed.

Content-Type has to be application/json. My CDN was serving it as text/plain. Took me a while to catch that one.

Use Google's verification tool. I should have used this from the start instead of guessing. It tells you exactly what's wrong.

The signing key situation

If you're a web developer, Android signing keys are confusing. There are two keys involved:

  1. Your upload key — you use this to sign the bundle before uploading to Play Console
  2. Google's app signing key — Google uses this to sign the APK that actually gets distributed to users

For your asset links JSON, you need #2. Not #1. I keep repeating this because it took me an embarrassingly long time to figure out, and every tutorial I found either glossed over it or assumed you already knew.

Generate your upload key:

keytool -genkey -v -keystore upload-keystore.jks \
  -keyalg RSA -keysize 2048 -validity 10000 \
  -alias upload
Enter fullscreen mode Exit fullscreen mode

Then do your first upload to Play Console, and THEN go grab the app signing key fingerprint. You can't get it before the first upload because Google generates it at that point.

Closed testing: the 12-tester gate

Once the app was building and the blue bar was gone, I thought I was done. Nope.

Google requires new apps to have 12 testers opted into a closed beta for 14 continuous days before you can go to production. You need real people with real Google accounts who join your testing program, install the app, and stay opted in for the full two weeks.

If you've got an existing user base, this is nothing. If you're a solo dev shipping your first app, it's a real bottleneck. I wrote a whole post about this already.

What I'd do differently next time

Get asset links working BEFORE you build the TWA. Host the JSON file, verify it with Google's tool, confirm it's correct. Then build the app. Debugging both at the same time is miserable.

Start looking for testers before your build is ready. The 14-day clock is the actual bottleneck, not the technical work.

Test on a Samsung phone early. My app looked perfect on a Pixel. Then I tried it on an older Galaxy and some things were off.

Is it worth it though?

Yeah. For a PWA that's already working well, a TWA is the right call. One codebase, Play Store presence, native-feeling experience. The setup takes a weekend if you know the pitfalls. Compared to maintaining a separate React Native or Kotlin app? Not even a question.

The downside is you're dependent on Chrome being installed, but that's basically every Android phone. And you need to keep your asset links in sync when keys change. That's about it.


I'm going through the closed testing process now with Money Me. If you've got an Android phone and don't mind helping an indie dev clear the 12-tester gate, I'd massively appreciate it. Beta testers get 6 months of Premium free.

👉 Join the beta on Google Play

30 seconds to opt in. If you've got TWA questions, drop them in the comments — I've made most of the mistakes already.

Top comments (1)

Collapse
 
markusbnet profile image
Mark Barnett

If you're curious about the backstory on why I built this in the first place (and yes, I know there are hundreds of budgeting apps already): dev.to/markusbnet/why-i-built-my-o...