{"id":173,"date":"2019-07-22T22:22:19","date_gmt":"2019-07-22T22:22:19","guid":{"rendered":"https:\/\/www.antpace.com\/blog\/?p=173"},"modified":"2025-08-25T13:57:13","modified_gmt":"2025-08-25T13:57:13","slug":"develop-apps-explore-the-world","status":"publish","type":"post","link":"https:\/\/www.antpace.com\/blog\/develop-apps-explore-the-world\/","title":{"rendered":"Develop Apps and Explore the World Wide Web"},"content":{"rendered":"<h2>World Wide Web<\/h2>\n<p>The web, as a platform, is open and free. Unlike native app markets, we don&#8217;t have to wait for software to be approved by any third-party. It works across any device or operating system that has a web browser. (Which is why standards across browsers is so important). But, until recently web-apps faced limitations. Not having full access to a device&#8217;s hardware and operating system was an issue &#8211; but that&#8217;s being fixed as more native APIs are being added to modern web browsers.<\/p>\n<p>A disadvantage of having a web-only app was losing out on the discoverability that comes with having it listed in a searchable marketplace. Adding a web-app to your device home screen, from a web browser, is not intuitive to average users. Fortunately, the Google Play Market allows us to upload an app file that links to a progressive web app.<\/p>\n<p>This involves a new protocol,\u00a0<strong>Trusted Web Activities<\/strong>, as &#8220;<a href=\"https:\/\/developers.google.com\/web\/updates\/2019\/02\/using-twa\">a way to integrate\u00a0your\u00a0web-app content such as\u00a0your\u00a0PWA with\u00a0your\u00a0Android app<\/a>&#8220;. The PWA leverages\u00a0<strong>Digital Asset Links<\/strong>\u00a0to &#8220;<a href=\"https:\/\/developers.google.com\/digital-asset-links\/v1\/getting-started\">declare that it is associated with a specific Android app.<\/a>&#8221;<\/p>\n<h2>Progressive web apps<\/h2>\n<p>I decided to try this out with one of my web-apps, <a href=\"https:\/\/www.bjjtracker.com\/\" target=\"_blank\" rel=\"noopener\">BJJ Tracker<\/a>. <a href=\"https:\/\/www.antpace.com\/blog\/bjj-tracker\/\" target=\"_blank\" rel=\"noopener\">You can read about how I first built it on another blog post<\/a>.<\/p>\n<p>I had to make sure it qualified as a PWA. It needed offline support, as well as any other features that would make it feel like a native app. Google Chrome&#8217;s developer tools has a section called &#8220;Audits&#8221; that helped me identify such opportunities.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-180\" src=\"https:\/\/www.antpace.com\/blog\/wp-content\/uploads\/2019\/07\/Screen-Shot-2019-07-22-at-4.04.29-PM.png\" alt=\"progressive web app audit\" width=\"729\" height=\"907\" \/><\/p>\n<p>The first step was to create a &#8220;service worker&#8221; JavaScript file, and register it when BJJ Tracker loads.<\/p>\n<pre>if('serviceWorker' in navigator) {\n  navigator.serviceWorker\n           .register('\/serviceWorker.js')\n           .then(function() { console.log(\"Service Worker Registered\"); })\n           .catch(error =&gt; {\n\t        \tconsole.log(error.message)\n\t    \t})\n}<\/pre>\n<p>I added the above code to a shared file that loads on every page of my app.\u00a0 Below is an example service worker file.\u00a0This file downloads any vital assets to a user&#8217;s device, and later loads them from the cache. Including a <a href=\"https:\/\/github.com\/dominiccooney\/cache-polyfill\">polyfill<\/a> ensures that the cache methods exist (in case the browser does not support them natively). &#8220;<a href=\"https:\/\/developers.google.com\/web\/fundamentals\/codelabs\/offline#install_the_site_assets\">We need to use the polyfill because the Cache API is not yet fully supported in all browsers.<\/a>&#8221;<\/p>\n<pre>importScripts('\/cache-polyfill.js');\n\nself.addEventListener('install', function(e) {\n e.waitUntil(\n   caches.open('bjjtracker').then(function(cache) {\n    return cache.addAll([\n       '\/',\n       '\/index',\n       '\/index?login',\n       '\/create-record?class',\n       '\/create-record',\n       '\/create-record?competition',\n       '\/view-record',\n       '\/view-month',\n       '\/privacy-policy',\n       '\/contact',\n       '\/view-more-data',\n       '\/account',\n       '\/css\/bootstrap.min.css',\n       '\/css\/bootstrap.min.css',\n       '\/css\/bootstrap-theme.min.css',\n       '\/css\/main.css',\n       '\/simpleMobileMenu\/styles\/jquery-simple-mobilemenu.css',\n       'https:\/\/use.fontawesome.com\/releases\/v5.3.1\/css\/all.css',\n       'https:\/\/fonts.googleapis.com\/css?family=Roboto|Eczar&amp;display=swap',\n       'https:\/\/ajax.googleapis.com\/ajax\/libs\/jquery\/1.11.2\/jquery.min.js',\n     ]);\n    }).catch(error =&gt; {\n        console.log(error.message)\n    })\n );\n});\n\nself.addEventListener('fetch', function(event) {\n\tevent.respondWith(\n\t\tcaches.match(event.request).then(function(response) {\n\t\t\treturn response || fetch(event.request);\n\t\t}).catch(error =&gt; {\n\t        console.log(error.message)\n\t    })\n\t);\n});\n<\/pre>\n<p><a href=\"https:\/\/developers.google.com\/web\/ilt\/pwa\/caching-files-with-service-worker\">Read the documentation on Google&#8217;s developer portal.<\/a><\/p>\n<p>Next, I created a &#8220;manifest&#8221; file. This file is written in JSON format. It helps describe how the web-app behaves once &#8220;installed&#8221;. It handles things such as app icon images and meta data.<\/p>\n<pre>{\n  \"name\": \"BJJ Tracker\",\n  \"lang\": \"en-US\",\n  \"short_name\": \"BJJ Tracker\",\n  \"start_url\": \"\/\",\n  \"display\": \"standalone\",\n  \"background_color\": \"#2a4d69\",\n  \"theme_color\": \"#2a4d69\",\n  \"description\": \"Track Brazilian Jiu Jitsu progress and fitness goals.\",\n  \"icons\": [{\n    \"src\": \"img\/homescreen48.png\",\n    \"sizes\": \"48x48\",\n    \"type\": \"image\/png\"\n  }, {\n    \"src\": \"img\/homescreen72.png\",\n    \"sizes\": \"72x72\",\n    \"type\": \"image\/png\"\n  }, {\n    \"src\": \"img\/homescreen96.png\",\n    \"sizes\": \"96x96\",\n    \"type\": \"image\/png\"\n  }, {\n    \"src\": \"img\/homescreen144.png\",\n    \"sizes\": \"144x144\",\n    \"type\": \"image\/png\"\n  }, {\n    \"src\": \"img\/homescreen168.png\",\n    \"sizes\": \"168x168\",\n    \"type\": \"image\/png\"\n  }, {\n    \"src\": \"img\/homescreen192.png\",\n    \"sizes\": \"192x192\",\n    \"type\": \"image\/png\"\n  }, {\n    \"src\": \"img\/homescreen512.png\",\n    \"sizes\": \"512x512\",\n    \"type\": \"image\/png\"\n  }]\n}\n<\/pre>\n<p>I created the image assets <a href=\"https:\/\/www.gimp.org\/\">using open source software<\/a>.<\/p>\n<figure id=\"attachment_517\" aria-describedby=\"caption-attachment-517\" style=\"width: 1903px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-517\" src=\"https:\/\/www.antpace.com\/blog\/wp-content\/uploads\/2019\/07\/image-assets.png\" alt=\"Image assets created with GIMP\" width=\"1903\" height=\"880\" \/><figcaption id=\"caption-attachment-517\" class=\"wp-caption-text\">Image assets created with GIMP<\/figcaption><\/figure>\n<p>The manifest needs to be referenced by the app. I added a link tag to a shared &lt;<em>head<\/em>&gt; file. Additionally, I included a few other meta tags that let browsers know to treat this website as an app.<\/p>\n<pre>&lt;link rel=\"manifest\" href=\"\/manifest.json\"&gt;\n&lt;meta name=\"theme-color\" content=\"#005b96\"\/&gt;\n&lt;meta name=\"mobile-web-app-capable\" content=\"yes\"&gt;\n&lt;meta name=\"apple-mobile-web-app-capable\" content=\"yes\"&gt;\n&lt;meta name=\"msapplication-starturl\" content=\"\/\"&gt;\n<\/pre>\n<h2>Android Studio<\/h2>\n<p>A signed app bundle is generated from Android Studio.\u00a0 I<a href=\"https:\/\/github.com\/GoogleChromeLabs\/svgomg-twa\/\"> use a sample project from Google Chrome Labs as a template<\/a>. We can clone that repository, and update the &#8220;<em><span class=\"s1\">\/svgomg-twa\/<\/span>app\/build.gradle<\/em>&#8221; settings to point to our PWA.<\/p>\n<figure id=\"attachment_538\" aria-describedby=\"caption-attachment-538\" style=\"width: 882px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-538 size-full\" src=\"https:\/\/www.antpace.com\/blog\/wp-content\/uploads\/2019\/07\/app-gradle-settings.png\" alt=\"app gradle settings\" width=\"882\" height=\"389\" \/><figcaption id=\"caption-attachment-538\" class=\"wp-caption-text\">TWA to wrap SVGOMG in an Android App<\/figcaption><\/figure>\n<p>The app&#8217;s icon files <a href=\"https:\/\/romannurik.github.io\/AndroidAssetStudio\/icons-launcher.html\">can be generated using an online tool<\/a>. The downloadable bundle can be dropped into &#8220;<span class=\"s1\"><em>\/svgomg-twa\/app\/src\/main\/res\/<\/em>&#8220;.<\/span><\/p>\n<figure id=\"attachment_542\" aria-describedby=\"caption-attachment-542\" style=\"width: 1888px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-542\" src=\"https:\/\/www.antpace.com\/blog\/wp-content\/uploads\/2019\/07\/icon-generator.png\" alt=\"icon generator\" width=\"1888\" height=\"899\" \/><figcaption id=\"caption-attachment-542\" class=\"wp-caption-text\">https:\/\/romannurik.github.io\/AndroidAssetStudio\/icons-launcher.html<\/figcaption><\/figure>\n<p>When creating the app bundle (&#8220;Build &gt; Generate Signed Bundle\/APK&#8221;) we&#8217;ll need a signing key. I created a new one, and named the file <em>mykeystore<\/em>.<em>keystore<\/em>.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-548 size-full\" src=\"https:\/\/www.antpace.com\/blog\/wp-content\/uploads\/2019\/07\/key-signing.png\" alt=\"key signing on mac\" width=\"742\" height=\"582\" \/><\/p>\n<p>An\u00a0&#8220;<em>assetlinks.json<\/em>&#8221; file needs to be uploaded to the web app&#8217;s host to satisfy the\u00a0Digital Asset Links requirement.\u00a0 &#8220;<a href=\"https:\/\/developers.google.com\/digital-asset-links\/v1\/getting-started\">The Digital Asset Links protocol and API enable an app or website to make public, verifiable\u00a0<em>statements<\/em>\u00a0about other apps or websites.<\/a>&#8221;\u00a0This confirms ownership of the PWA so that it can be linked to our app in the Play Store. To generate this file, first we&#8217;ll need to get the fingerprint from the signing key we used:<\/p>\n<pre>keytool -list -v -keystore mykeystore.keystore -alias mykeystore -storepass password-here  -keypass password-here\n<\/pre>\n<p>That command shows us the\u00a0<span class=\"s1\">certificate fingerprints. Copy the <\/span><em><span class=\"s1\">SHA256<\/span><\/em><span class=\"s1\"> value. It is used with <a href=\"https:\/\/developers.google.com\/digital-asset-links\/tools\/generator\">Google&#8217;s Statement List Generator<\/a> to create the contents of the\u00a0<em>assetlinks.json<\/em> file. The statement file is then placed in a &#8220;<em>.well-known&#8221;<\/em> directory on the root of our PWA domain (<em>eg.<\/em> <em>https:\/\/www.bjjtracker.com\/.well-known\/assetlinks.json<\/em>)<\/span><\/p>\n<p>Finally, I visited the Google Play Console. Besides uploading the .<em>apk<\/em> file, I also needed to include screenshots, featured image files, and complete a content rating survey &#8211; amongst other things. <a href=\"https:\/\/play.google.com\/store\/apps\/details?id=xyz.appmaker.lbpvgo\">Since my app has been approved, you can now find it in the Google Play Market<\/a>.<\/p>\n<p><a href=\"https:\/\/play.google.com\/store\/apps\/details?id=xyz.appmaker.lbpvgo\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-544 size-full\" src=\"https:\/\/www.antpace.com\/blog\/wp-content\/uploads\/2019\/07\/Google-Play-Store.png\" alt=\"BJJ Tracker in the Google Play Store.\" width=\"1230\" height=\"811\" \/><\/a><\/p>\n<p>This app is a side project I use to toy with new web technologies. I&#8217;m trying to drive traffic to it so that I can experiment with optimizing conversions. I&#8217;m using it as a trial grounds for another software service called <a href=\"https:\/\/www.splitwit.com\" target=\"_blank\" rel=\"noopener\">SplitWit<\/a>. SplitWit is focused on optimizing conversions for the web, and helping digital marketers reach their goals. <a href=\"https:\/\/www.antpace.com\/blog\/split-testing-optmization\/\">You can read about it on another post from this blog<\/a>.<\/p>\n<p><a href=\"https:\/\/www.instagram.com\/p\/BzemsvWJTO0\/\" target=\"_blank\" rel=\"noopener\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-184\" src=\"https:\/\/www.antpace.com\/blog\/wp-content\/uploads\/2019\/07\/bjj-tracker-ig.jpg\" alt=\"bjj tracker app\" width=\"750\" height=\"750\" \/><\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>World Wide Web The web, as a platform, is open and free. Unlike native app markets, we don&#8217;t have to wait for software to be approved by any third-party. It works across any device or operating system that has a web browser. (Which is why standards across browsers is so important). But, until recently web-apps &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/www.antpace.com\/blog\/develop-apps-explore-the-world\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Develop Apps and Explore the World Wide Web&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":3149,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[11,86,89,101,119,140,144],"class_list":["post-173","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-web-development","tag-apps","tag-mobile","tag-open-source","tag-pwa","tag-software","tag-web-development","tag-world-wide-web"],"_links":{"self":[{"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/posts\/173","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/comments?post=173"}],"version-history":[{"count":1,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/posts\/173\/revisions"}],"predecessor-version":[{"id":3150,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/posts\/173\/revisions\/3150"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/media\/3149"}],"wp:attachment":[{"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/media?parent=173"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/categories?post=173"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/tags?post=173"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}