Building Progressive Web Applications

Aman Raj

05 Jun 2017

Building Progressive Web Applications

With, Progressive Web Applications (PWAs), developers can deliver amazing app-like experiences to users using modern web technologies. It is an emerging technology designed to deliver functionalities like push notifications, working offline etc to deliver a rich mobile experience.In this post, we will learn how to make your Django application progressive that works offline.

 

What are progressive web applications?

Progressive web applications are web applications with the rich user experience. They make smooth user interaction with web and have the following features :

A force that drives progressive web applications

The core concept behind progressive web app is service worker. Service worker is a script which browser runs in the background without interfering with the user’s interaction with the application. Some of the work done by this script include push notification, content caching, data synch when the internet connection is available.

 

There are a lot more about progressive web applications. But in this post, we are more concerned with connectivity independent feature. We will learn how service workers help us to get the things done. This post is going to be about making a Django application work offline. We will use charcha as our reference application.

Let’s get started without any further delay.

 

Register a service worker

To bring service worker in the loop, we need to register it in our application. Basically, it tells the browser where it needs to look for the service worker script. Put the following code snippet in your javascript file, it gets loaded as soon as your application runs.

 

 

 var serviceWorkerPath = "/charcha-serviceworker.js";
 var charchaServiceWorker = registerServiceWorker(serviceWorkerPath);
 function registerServiceWorker(serviceWorkerPath){
    if('serviceWorker' in navigator){
      navigator.serviceWorker
        .register(serviceWorkerPath)
          .then(
            function(reg){
              console.log('charcha service worker registered');
            }
          ).catch(function(error){
            console.log(error)
          });
    }
 }

 

How does the above code work?

Launching your Django application

Now go ahead and launch your Django application. Open the browser console and if you are able to see the above-mentioned error, that means you should be grateful to your browser for having service worker support. Okay! that being said, we encountered this error, which means something is wrong. Reload your application and you will see a detailed error message as mentioned below

service worker registration failure

This is because Django could not find anything to render for request /charcha-serviceworker.js.
Our next step is to define a URL path and call a view for this path.

 

  url(r'^charcha-serviceworker(.*.js)$', views.charcha_serviceworker, name='charcha_serviceworker'),
 def charcha_serviceworker(request, js):
      template = get_template('charcha-serviceworker.js')
      html = template.render()
      return HttpResponse(html, content_type="application/x-javascript")

what we are doing above is, just returning our Charcha-serviceworker.js whenever a request is made to download this file.

 

TEMPLATES = [
    {
        ...
        'DIRS': [os.path.join(PROJECT_ROOT, 'templates'), os.path.join(PROJECT_ROOT, ' '),],
        ...
        ...
    },
]

Notice os.path.join(PROJECT_ROOT, 'templates') was existing template path and os.path.join(PROJECT_ROOT, ' ') was added and hence enabling get_template function to search for script file in the project root directory.

 

Now re run your application and if you do not see the previous error, that means you are good to go with writing service worker script.

 

 

Install the service worker

When a service worker is registered, an install event is triggered the very first time an user visits the application page. This is the place where we cache all the resources needed for our application to work offline.
Open the previously created charcha-serviceworker.js and define a callback function which listens for install event.

var cacheName = 'charcha';
var filesToCache = [
'/',
];
self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open(cacheName).then(function(cache) {
      return cache.addAll(filesToCache);
    })
  );
});

Let us see what’s going on here

  1. It adds an eventListener which listens for the install event.
  2. Opens a cache with the name charcha.
  3. Adds an array of files which need to be cached and pass it to cache.addAll() function.
  4. event.waitUntil waits untill the caching promise is resolved or rejected.
  5. Service worker is considered successfully installed only when it caches all the files successfully. If it fails to download and cache even a single file, the installation fails and browser throws away this service worker.

Install occurs only once per service worker unless you update and modify the script. In that case, a fresh new service worker will be created without altering the existing running one. We will discuss it more in a later phase which is update phase of a service worker.

 

Rerun your application and open chrome dev tools. Open Service Workers pane in the Application panel. You will find your service worker with running status. It will show something like below to make sure that the service worker is successfully installed.

service worker installed

You will see a list of cached file in Cache Storage pane under Cache in same Application panel.

NOTE : We can add all the static resources like css files, image files in the list of files to be cached. But dynamic url like /discuss/{post_id} cannot be defined in this list. So how to cache wildcard urls. We will see it in the next section.

 

Intercept the request and return cached resources

Since we have installed our service worker , we are ready to roll and return responses from our cached resources. Once the service worker is installed, every new request triggers a ‘fetch’ event. We can write a function which listen for this event and responds with the cached version of the requested resource. Our fetch event listener is going to look like

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request).then(function(response) {
      return response || fetch(event.request);
    })
  );
});

Through the above code snippet, we are trying to fetch the requested resource from the list of cached reources. If it finds, the resource is returned from the cache, otherwise, we make a network request to fetch the resource and return it.

 

In the previous section, we saw an issue of caching dynamic urls. Let’s see how we can resolve that inside fetchevent listener. Modify the previous code for fetch event to look like

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request)
      .then(function(response) {
        if (response) {
          return response;
        }
        var fetchRequest = event.request.clone();
        return fetch(fetchRequest).then(
          function(response) {
            if(!response || response.status !== 200 || response.type !== 'basic') {
              return response;
            }
            var responseToCache = response.clone();
            caches.open(cacheName)
              .then(function(cache) {
                cache.put(event.request, responseToCache);
              });
            return response;
          }
        );
      })
    );
});

Let’s walk through the code to see how it solves the aforementioned issue.

 

How the code solves the aforementioned issue

  1. Only if the requested resource is available in the cache, a cache hit occurs and resource is returned from cache.
  2. In case the cache hit does not occur with the requested resource (which will be the case with dynamic urls), a fresh fetch request is made to fetch the resource from the network.
  3. If the response is not valid, status is not 200 or response type is not basic, the response is returned because error responses need not to be cached.
  4. In case the response is valid, status is 200 and response type is basic, this response is first saved into a cached list and then returned to the client.

You might wonder why are we clonning request and response. Basically, a web request/response is a stream. It can be consumed by only one. In our code, we are consuming request/response for both caching and calling a fetch event. Once read, the stream is gone and will not be available for next read. In order to get a fresh request/response stream again, we clone the request/response.

 

Now with the above fetch event listener, we are sure that each and every new requested resource is cached and returned when a request is made.
At this stage, we are good to go and test the offline running capability of our application. To test it follow these steps:

 

  1. Rerun the application and browse through a few pages.
  2. Open chrome dev tools and open Service Workers pane in Application panel.
  3. Enable offline check box. After enabling it, you will see a yellow warning icon. This indicates that your application has gone offline.
  4. Now go and browse through all those pages which you previously visited and were cached. You will be able to access those pages without any network error.

 

Activate the service worker

service worker waiting
self.addEventListener('activate', function(e) {
  e.waitUntil(
    caches.keys().then(function(keyList) {
      return Promise.all(keyList.map(function(key) {
        if (key !== cacheName) {
          return caches.delete(key);
        }
      }));
    })
  );
});

Now you can Go Gaga over it because you have managed to make your django application work offline.

 

Summary

You can make Django web apps work faster, more engaging and also work offline. You just need to have support for sevice worker’s in your target browser.

There is more of a progressive web app and service worker. Hang around because in further posts we will discuss

 


Have a question?

Need Technology advice?

Connect

+1 669 253 9011

contact@hashedin.com

facebook twitter linkedIn youtube