Android

Converting a Website into an Android App with Bubblewrap

Modern users often prefer the convenience of mobile apps over browsing websites through a browser. If you already have a progressive web app (PWA) or a mobile-friendly website, you can quickly transform it into an Android app without rewriting everything from scratch. One of the most efficient tools for this purpose is Bubblewrap, a command-line utility developed by Google that packages PWAs into Trusted Web Activities (TWAs).

This article is based on a Jakarta Faces and OmniFaces project, where OmniFaces helps us add Progressive Web App (PWA) features. However, the final steps with Bubblewrap are the same regardless of whether you are using Jakarta Faces or any other web framework.

1. What is a Progressive Web App (PWA)?

A Progressive Web App (PWA) is a web application that leverages modern web capabilities to deliver an app-like experience directly through the browser. Unlike traditional websites, PWAs can be installed on a user’s home screen, work offline, and send push notifications.

Key features of PWAs include:

  • Installability: Users can install them just like native apps.
  • Offline Support: Thanks to service workers, PWAs can work without an internet connection.
  • Responsive Design: They adapt to different screen sizes and devices.
  • Engagement Features: PWAs can send push notifications and re-engage users.

2. What is Bubblewrap?

Bubblewrap is a CLI (command-line interface) tool that enables us to wrap our PWA (Progressive Web Application) into an Android application that runs in a Trusted Web Activity. Unlike hybrid frameworks that embed your site in a WebView, TWAs enable your PWA to run in Chrome with access to advanced web capabilities, such as service workers, offline caching, and push notifications. This means your Android app behaves almost exactly like a native app while still relying on your website’s content.

3. What is a Trusted Web Activity (TWA)?

A Trusted Web Activity (TWA) is a way to run your PWA inside an Android application using Chrome as the rendering engine. Unlike embedding a website in a WebView, TWAs provide:

  • Full PWA Capabilities: Features like offline caching, push notifications, and background sync work.
  • Performance and Security: TWAs rely on Chrome, ensuring speed and security.
  • Full-Screen Experience: Your web app runs in immersive full-screen mode without browser UI elements.
  • Play Store Distribution: You can publish your TWA as a native app on Google Play.

Essentially, TWAs bridge the gap between web and native apps, letting us reuse our PWA while delivering a native-like Android experience.

4. Prerequisites

Before you begin, ensure you have the following installed:

  • Node.js (v14.15.0 or higher)
  • Java Development Kit (JDK 11 or higher)
  • Android Studio (for building and testing the app)
  • Keytool (comes with the JDK, used for signing the app)
  • A Progressive Web App (PWA) with a valid Web App Manifest

You should also ensure your website:

  • Uses HTTPS
  • Has a valid manifest.webmanifest or manifest.json file with icons and a start URL
  • Implements a service worker for offline capabilities

5. Using OmniFaces to Create a Progressive Web App

When building a Jakarta Faces project, OmniFaces provides built-in support for generating a Progressive Web App (PWA) manifest. Instead of manually creating a manifest.webmanifest or manifest.json, you can extend OmniFaces’ WebAppManifest class in a CDI bean. This gives you full flexibility to define app details, icons, offline pages, and categories, all integrated with your Jakarta Faces application.

Below is an example class that extends WebAppManifest and configures PWA properties:

@ApplicationScoped
public class PWABean extends WebAppManifest {

    @Override
    public String getName() {
        return "Faces PWA";
    }

    @Override
    public String getShortName() {
        return "FPWA";
    }

    @Override
    public Collection<ImageResource> getIcons() {
        return Arrays.asList(
                ImageResource.of("images/android-chrome-192x192.png", Size.SIZE_192),
                ImageResource.of("images/android-chrome-512x512.png", Size.SIZE_512)
        );
    }

    @Override
    public String getThemeColor() {
        return "#cc9900";
    }

    @Override
    public String getBackgroundColor() {
        return "#ffffff";
    }

    @Override
    public Display getDisplay() {
        return Display.STANDALONE;
    }

    @Override
    public Collection<Category> getCategories() {
        return Arrays.asList(Category.MAGAZINES, Category.SPORTS);
    }

    @Override
    public Collection<RelatedApplication> getRelatedApplications() {
        return Arrays.asList(
                RelatedApplication.of(Platform.PLAY, "https://play.google.com/store/apps/details?id=com.jcg.example.app", "com.example.app1"),
                RelatedApplication.of(Platform.ITUNES, "https://itunes.apple.com/app/app-1/id8672435169")
        );
    }

    @Override
    public Collection<String> getCacheableViewIds() {
        return Arrays.asList("/index.xhtml");
    }
    
    @Override
    protected String getOfflineViewId() {
        return "/offline.xhtml";
    }
}
  • App name & short name: Defines how the app appears on the home screen.
  • Icons: References PWA icons (placed in /resources/images/). Bubblewrap will use these when generating the Android app.
  • Theme & background colors: Define app styling for the splash screen and browser UI.
  • Display mode: STANDALONE makes the PWA launch like a native app.
  • Categories & related apps: Provides metadata for app stores.
  • Cacheable view IDs: Specifies which JSF pages can be cached for offline usage.
  • Offline view: Points to a fallback page when offline.

Reference the Generated Manifest

Once the Bean is active, OmniFaces automatically generates a manifest.webmanifest file at the virtual resource location omnifaces:manifest.webmanifest. You just need to link it in your main template (e.g., index.xhtml):

<!DOCTYPE html>
<html lang="en"
    xmlns:f="jakarta.faces.core"
    xmlns:h="jakarta.faces.html"
    xmlns:p="primefaces"
>
    <h:head>
        <title>Hello</title>
        <link rel="manifest" href="#{resource['omnifaces:manifest.webmanifest']}" crossorigin="use-credentials" />
    </h:head>
    <h:body>
        <h1>Hello</h1>
        
    </h:body>
</html>

  • The <link rel="manifest"> tag loads the PWA manifest dynamically from OmniFaces.
  • The crossorigin="use-credentials" attribute ensures secure fetching of the manifest with credentials if needed.

With this setup, your Jakarta Faces + OmniFaces application becomes a valid PWA. Once the PWA is working, you can proceed with the Bubblewrap steps to package it as an Android app.

Install Bubblewrap

Bubblewrap is distributed as an npm package. Install it globally by running:

npm install -g @bubblewrap/cli

Check if the installation was successful:

bubblewrap --version

If installed correctly, the version number will be displayed.

Initialize Your Project

Once you have installed Bubblewrap, the next step is to initialise your project. This process connects your Progressive Web App’s manifest to Bubblewrap and generates the Android project structure that will eventually become your installable mobile app.

To begin, you should create or move into an empty directory and run the initialisation command. Substitute yourwebsite.com with the actual URL of your site.

bubblewrap init --manifest=https://yourwebsite.com/manifest.webmanifest

When you execute this command, Bubblewrap first fetches your manifest.webmanifest file from the URL you provide. This manifest contains vital information such as the application name, short name, theme color, start URL, and icons.

Bubblewrap reads these values and uses them to pre-populate your Android app’s metadata. For example, a typical manifest might look like this:

{
  "background_color": "#ffffff",
  "cacheable_view_ids": [
    "/index.xhtml"
  ],
  "categories": [
    "magazines",
    "sports"
  ],
  "dir": "auto",
  "display": "standalone",
  "icons": [
    {
      "sizes": "192x192",
      "src": "/jakarta.faces.resource/images/android-chrome-192x192.png.xhtml?v=1757403291000",
      "type": "image/png"
    },
    {
      "sizes": "512x512",
      "src": "/jakarta.faces.resource/images/android-chrome-512x512.png.xhtml?v=1757403291000",
      "type": "image/png"
    }
  ],
  "lang": "en-CA",
  "name": "Faces PWA",
  "prefer_related_applications": false,
  "related_applications": [
    {
      "id": "com.example.app1",
      "platform": "play",
      "url": "https://play.google.com/store/apps/details?id=com.jcg.example.app"
    },
    {
      "platform": "itunes",
      "url": "https://itunes.apple.com/app/app-1/id8672435169"
    }
  ],
  "screenshots": [],
  "short_name": "FPWA",
  "shortcuts": [],
  "start_url": "https://localhost:8443/",
  "theme_color": "#cc9900"
}

After pulling the manifest, Bubblewrap starts an interactive setup process in your terminal. Here, it will ask you a series of questions to finalise the configuration of your Android app. For instance, it will prompt you for the application ID, application name, launcher name, start URL, icon location, key store location, and your organisation details.

The terminal session looks something like this:

Domain: Press Enter (auto-filled from your manifest)

Application name: Your full app name

Application ID: (e.g, chat.yourapp.twa)

Include support for Play Billing?: Type Y if your app uses Google Play in-app purchases. Otherwise, N

Request geolocation permission?: Type Y if your app needs location access. Otherwise, N
.........
Password for key store: Enter a new password

Password for key: Re-enter the same password

Among these questions, the most important details are the keystore and its key, and both must share the same password to avoid errors. During this setup, Bubblewrap also checks if you have the necessary dependencies installed. Specifically, it requires the Java Development Kit (JDK) and the Android SDK, which are essential for compiling and building the final Android application.

If these are not already available on your machine, Bubblewrap will offer to install them for you. The prompts look like this:

? Do you want Bubblewrap to install the JDK (recommended)?
  (Enter "No" to use your own JDK 17 installation) No
? Path to your existing JDK 17: 
? Do you want Bubblewrap to install the Android SDK (recommended)? 
 (Enter "No" to use your own Android SDK installation) (Y/n)

After you finish answering all the questions and setting up the required dependencies, Bubblewrap creates the files and folders that form the foundation of your Android project. By automatically configuring the JDK and Android SDK, it ensures that your development environment is fully prepared for the build process.

Build the App

Run the build command to create an Android project:

bubblewrap build --universalApk

Running this command compiles your Progressive Web App into either an .apk (Android Package) or an .aab (Android App Bundle). An APK is a directly installable file you can use for testing on devices or emulators, while an AAB is the publishing format required by the Google Play Store, allowing it to generate optimised APKs for different devices.

The initial build may take longer while dependencies are downloaded, but once finished, the APK is placed in the output directory and can be tested on either a physical Android device or an emulator.

Setting Up TWA Validation

Before your Android app can open your Progressive Web App inside a Trusted Web Activity (TWA), Google requires you to set up a validation process called Digital Asset Links. This step ensures that your app and your website are linked securely, allowing your app to display your web content in full-screen mode without browser UI elements. Without this validation, the app may fall back to opening your PWA in a regular Chrome Custom Tab instead of a seamless TWA.

To configure TWA validation, you need to create an assetlinks.json file and host it on your domain under the .well-known directory:

https://your-domain.com/.well-known/assetlinks.json

A typical assetlinks.json looks like this:

[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.example.facespwa",
    "sha256_cert_fingerprints": [
      "12:34:56:78:90:AB:CD:EF:12:34:56:78:90:AB:CD:EF:12:34:56:78:90:AB:CD:EF:12:34:56:78:90:AB:CD:EF"
    ]
  }
}]

In this file:

  • The package_name must match the application ID you used during bubblewrap init.
  • The sha256_cert_fingerprints value corresponds to the fingerprint of the signing key used to sign your app. You can retrieve it with the following command:
keytool -list -v -keystore keystore.jks -alias facesKey -keypass yourpassword

Once generated, place the assetlinks.json file at /.well-known/ on your server. This allows the Play Store and Chrome to verify the relationship between your website and your app. Additionally, there is Google’s official Digital Asset Links Generator tool. This online tool makes it easier to create and verify your assetlinks.json, ensuring your TWA validation is correct.

6. Conclusion

In this article, we showed how to convert your website into an Android app using Bubblewrap. We covered creating a PWA with OmniFaces, initialising and building the app with Bubblewrap. While the example used Jakarta Faces, the final Bubblewrap steps work with any web framework, making it a practical way to bring web apps to the Google Play Store.

7. Download the Source Code

This article explored how to convert your website into an Android app using Bubblewrap.

Download
You can download the full source code of this example here: Sign up

Omozegie Aziegbe

Omos Aziegbe is a technical writer and web/application developer with a BSc in Computer Science and Software Engineering from the University of Bedfordshire. Specializing in Java enterprise applications with the Jakarta EE framework, Omos also works with HTML5, CSS, and JavaScript for web development. As a freelance web developer, Omos combines technical expertise with research and writing on topics such as software engineering, programming, web application development, computer science, and technology.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button