Create a PWA in Xamarin.Forms with Ooui.Wasm

Ooui is a framework created by Frank Krueger, that lets you run Xamarin.Forms in a web browser. It actually has two ways of doing this, Ooui.AspNetCore, which is server side, or client side, using Ooui.Wasm.

WASM is short for WebAssembly and to put it simply, it lets you run compiled code in the browser. Mono was brought to WebAssembly with mono-wasm, that allows us to run C# on in WebAssembly. This is what Ooui.Wasm uses. At this point, we could now run Ooui, in WASM. To take this one step further, I have now created an example of how to load this in a PWA.

Create Project

  1. Create a .NET Core Console Application
  2. Add these references to your Application.
    Xamarin.Forms
    Ooui.Wasm
    Ooui.Forms
    
  3. Add a new ContentPage called MainPage to your app. (Yes we can use XAML.)
  4. Add the following code to the Program.cs Main function.
    static void Main(string[] args)
    {
        Forms.Init();
    
        var mainPage = new MainPage();
        UI.Publish("/", mainPage.GetOouiElement());
    }
    

Project Output

If you run this project, it will now output files in this directory.

\bin\Debug\netcoreapp2.0\dist

In here you will have

  1. index.html
  2. mono.js
  3. mono.wasm
  4. ooui.js
  5. managed [a folder with the .NET dlls]

This can be loaded in the browser, without any server side technology. It is a number of static files.

Note: If you want to run these locally, you need to run it from a server, not the local file system, due to some browser restrictions. Personally I use Web Server for Chrome, you just launch that, point it as the dist directory from above, and open the server from the link provided. Its nice and simple.

This in itself is incredible. A Xamarin.Forms app, with XAML, running from static files, inside a browser, as a client side application. But lets take this further.

Progressive Web App (PWA)

A progressive web app has many benefits but a few of them include:

  • Being able to install as an app on a mobile phone, with an icon on the home screen.
  • Can run offline

Create PWA

Just so I don’t mess with the existing files, in my project I have created a new folder called dist, and added folder icons, and files, manifest.json, pwa.html and service-worker.js.

I’ll explain each of these in the following steps. All of these have Copy to Output Directory as Always, so they come out in the dist directory.

PWA HTML

I took the automatically generated index.html, and have modified it, to load a service-worker (a requirement to being seen as a PWA), added a link to the manifest.json, and added a title.

<!DOCTYPE html>
<html>
    <head>
        <title>Xamarin.Forms PWA</title>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css" />
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
        <link rel="manifest" href="manifest.json">
    </head>
    <body>
        <div id="ooui-body" class="container-fluid">
            <p id="loading"><i class="fa fa-refresh fa-spin" style="font-size:14px;margin-right:0.5em;"></i> Loading...</p>
        </div>
        <script defer type="text/javascript" src="ooui.js"></script>
        <script type="text/javascript">
            if ('serviceWorker' in navigator) {
                window.addEventListener('load', function () {
                    navigator.serviceWorker.register('service-worker.js').then(function (registration) {
                        // Registration was successful
                        console.log('ServiceWorker registration successful with scope: ', registration.scope);
                    }, function (err) {
                        // registration failed :(
                        console.log('ServiceWorker registration failed: ', err);
                    });
                });
            }
        </script>
        <script type="text/javascript">
            var assemblies = ["Microsoft.CSharp.dll","Mono.Security.dll","mscorlib.dll","MyPWA.dll","Newtonsoft.Json.dll","Ooui.dll","Ooui.Forms.dll","System.Core.dll","System.dll","System.Numerics.dll","System.Runtime.Serialization.dll","System.Xml.dll","System.Xml.Linq.dll","Xamarin.Forms.Core.dll","Xamarin.Forms.Platform.dll","Xamarin.Forms.Xaml.dll"];
            document.addEventListener("DOMContentLoaded", function(event) {
                oouiWasm("MyPWA", "MyPWA", "Program", "Main", assemblies);
            });
        </script>
        <script defer type="text/javascript" src="mono.js"></script>
    </body>
</html>

Manifest

A manifest.json file is required to let the PWA enabled browser know, about the app name, icons and the start url.

{
  "name": "Xamarin.Forms PWA",
  "short_name": "PWA",
  "icons": [
  {
    "src": "icons/icon-128x128.png",
    "sizes": "128x128",
    "type": "image/png"
  },
  {
    "src": "icons/icon-144x144.png",
    "sizes": "144x144",
    "type": "image/png"
  },
  {
    "src": "icons/icon-152x152.png",
    "sizes": "152x152",
    "type": "image/png"
  },
  {
    "src": "icons/icon-192x192.png",
    "sizes": "192x192",
    "type": "image/png"
  },
  {
    "src": "icons/icon-512x512.png",
    "sizes": "512x512",
    "type": "image/png"
  }
  ],
  "start_url": "pwa.html",
  "display": "standalone",
  "background_color": "#3498DB",
  "theme_color": "#3498DB"
}

I created the icons per the dimensions listed.

Service Worker

The service worker, enables caching and offline storage, access to push notifications and more. For this example, we are just going to have a very simple, caching service worker.

var CACHE_NAME = 'my-site-cache-v1';
var urlsToCache = [
    'pwa.html',
    'service-worker.js'
];

self.addEventListener('install', function (event) {
    // Perform install steps
    event.waitUntil(
        caches.open(CACHE_NAME)
            .then(function (cache) {
               console.log('Opened cache');
               return cache.addAll(urlsToCache);
            })
   );
});

self.addEventListener('fetch', function (event) {
    event.respondWith(
        caches.match(event.request)
           .then(function (response) {
              // Cache hit - return response
              if (response) {
                  return response;
          }
          return fetch(event.request);
        }
        )
  );
});

Note: One of the major issues I am seeing that needs to be overcome, is a link between Xamarin.Forms and the Service Workers for caching and notifications. It could be done, but will require work to create JavaScript functions to wire up to the Ooui outputted html.

Running The PWA

To run a PWA, we need a HTTPS capable webhost. I have just thrown it on my personal webhost with https access. These are just static files remember. I could even just throw these into Azure Storage. It doesn’t need to actually run anything, this all runs client side, in the browser. Just take the files inside the dist folder and upload them.

First I load up my app by opening up chrome and going to the browser. Remember to go to the pwa.html not index.html, as we are using my modified version.

It prompts me automatically to add to the home screen. If I click ADD TO HOME SCREEN, then I will get something like this on my home screen.

Offline Access

Now I turn off ALL communications, mobile data and wireless and click on my PWA app. It first shows a splash screen.

Then the app shows the loading icon, and it finally loads.

 

Summary

This is an incredible accomplishment by Frank and with the added benefit of PWAs, this goes to an almost insane level. Think about it, we have a Xamarin.Forms application, installed to the home screen through a browser (not the app store), that runs offline and completely client side.

I have to say, if anyone builds that bridge between Ooui and PWAs for service workers, this would be an awesome option for developing PWAs in the future.

GitHub Repo: xamarin-forms-pwa


Posted

in

by

Tags: