Building An Offline Web App : Offline Currency Converter

offline web app
offline web app

Building a web application is great. But building a web application which can run both offline and online seamlessly is awesome.You leverage Javascript, HTML, CSS… skills to build a custom application which works on your browser with or without internet connection. A few weeks ago I dove deeper into offline web applications with the courses offered by Udacity. After acquiring the basic skills, I built an offline web application. This application is a currency converter. It is very easy to implement. And we will be talking about how to implement it. Through out this post, the code snippets will be written in ES6 syntax or above.

What We Will Build

The offline web app we will build is a currency converter. A very simple currency converter. It will take two currencies, one to convert from and the other to convert to. The amount will be input and the conversion process will take place. After completion, results will be displayed properly.

To build the web app, this API will be used : Free Currency Converter API.  Requests will be sent to this API and the results will be processed with Javascript. We will focus mostly on the application logic, and not on the layout, I used SemanticUI for a little bit of styling.

Here is the source code for the demo application we will build. The complete offline web app is hosted here, in case you will like to try it before building yours.

Building an Offline Web App

First, you need to know, this tutorial is for beginners. Since our main Objective is to build an offline web app, we will focus on the most important parts of making a web application work offline. Focusing on these parts will help absorb the most important parts of the process of building offline web apps and ignoring the rest. Remember, the source code is on Github and the link is mentioned above. And, the offline web app is also hosted on Github and available at the link mentioned above. So here are the topics under which the application will be built.

  • Service Workers
  • Cache API
  • IndexDB

The Service Worker

Let’s talk about the role which service workers play in this offline web app. First, a recap of what a service worker is. A service worker is the backbone of offline web apps. It is a script which is able to intercept web requests from a web app and provide custom responses.

Here, is how we will use the service worker in this web app.

  • First, we register the service worker. The registration is done when the service worker’s install event fires. When registering it, we provide a set of URLs which are going to be called and the response will be saved by the cache. This is done in the sw.js file. On the first run of the app, there should be internet connection for the responses to be loaded in the cache.
self.addEventListener('install', (event) => {

    event.waitUntil(
        //I cache the currencies
        caches.open(cacheName).then((cache) => {
            return cache.addAll([
                './',
                './js/main/idbManager.js',
                './js/idb.js',
                './styles/semantic.min.css',
                './js/main/converter.js',
                './js/main/main.js',
                './js/main/serviceWorker.js',
                'https://free.currencyconverterapi.com/api/v5/currencies',
            ]);
        })
    )
});
  • The service worker will then listen to fetch events (When any web request is made by the app). When this event fires, it will first compare the request URL with the content of the cache. If there is a match, it responds with what was saved in the cache. Else, it performs the intended web request and responds with what is gotten from the internet. With this, we have implemented half of the offline functionality. Here is the code for this step.
self.addEventListener('fetch', (event) => {

    let url = event.request.url;

    event.respondWith(
        //Respond with the cached response or if no response is saved,
        //respond with result from the network.
        caches.match(event.request).then((response) => {
            if(response) return response;
            return fetch(event.request);
        })
    )
});

The Cache

The cache has already been used above. The cache’s role is to cache responses to web requests made by the web app. It works with the service worker to provide an offline experience. The code above demonstrates how the cache saves requests and provides responses.

Inside the converter.js file, every logic for currency conversion is written. The cache is made use of here.

  • As an offline web app, the currencies which are available for conversion should be loaded without internet. This is done with the cache’s help. A fetch to the currency converter API is made and the service worker’s fetch event is fired, and the content of the cache is sent. Here is the method to fetch the currencies.
getAllCurrencies(callBack)
    {
        fetch("https://free.currencyconverterapi.com/api/v5/currencies")
        .then(response => callBack(null, response))
        .catch(error => callBack(error, null));
    }

IndexDB

IndexDB is simply used to save data. It is a kind of NoSQL database. It will play an important role in making our currency converter web app really offline.

When the user selects the currencies he/she will want to convert, the conversion should be done both offline or online. Here is how we will accomplish this. Working with IndexDB is alot easier with this library which I use. All the code to query IDB is found in the file named idbmanager.js.

  • When a conversion is done for the first time, the conversion rate is queried from the API and saved in IndexDB. If it is not the first time for this conversion, its rate is gotten from IDB directly and used to calculate the conversion. With this approach of getting conversion rates saved locally first, the web app will be able to perform conversions offline. Provided that the conversion was performed online before. Here is the code to perform these conversions.
//Converts the currency
    convertCurrency(amount, fromCurrency, toCurrency, callBack)
    {
        fromCurrency = encodeURIComponent(fromCurrency);
        toCurrency = encodeURIComponent(toCurrency);
        const query = fromCurrency + '_' + toCurrency;

        //we build the URL
        const url = `https://free.currencyconverterapi.com/api/v5/convert?q=${query}&compact=ultra`;

        //Inquire IDB for the objec'ts query
        this._idbManager.getQueryValueByID(query, (error, value) => {

            if(error){
                callBack(error);
                return;
            }

            //if the value was not found in idb, query the internet
            if(!value){

                fetch(url)
                .catch(error => callBack(error))
                .then(results => 
                {
                    //Invoke's the call back method of the upper layer using this class after 
                    //converting the result to json.
                    results.json().then(jsonData => 
                        {
                            //save the value and the query in idb first
                            this._idbManager.saveQueryInDatabase(query, jsonData[query]);

                            let total = jsonData[query] * amount;
                            callBack(null, (Math.round(total * 100) / 100));
                        });
                });                
            }
            //If the value was found in idb
            else{
                //get the value of the query
                let val = value['value'];
                let total = val * amount;
                callBack(null, (Math.round(total * 100) / 100));
            }
            //value.value

        })

    }

Here is the code to perform database access with IndexDB.

export const databaseName = 'currency-converter-007damiendoumer-1';

export default class IDBManager{

    constructor(){
        this._idbPromise = this.setupDatabase();
    }

    setupDatabase(){
        // If the browser doesn't support service worker,
        // we don't care about having a database
        if (!navigator.serviceWorker) {
            return Promise.resolve();
        }

        return idb.open(databaseName, 1, upgrade => {
            let store = upgrade.createObjectStore(databaseName, {
                keyPath: 'query'
            });
            return store;
        });
    }

    //Save a query in index db
    saveQueryInDatabase(query, value){

        this._idbPromise.then((db) => {

            if(!db) return;

            let transaction = db.transaction(databaseName, 'readwrite');
            let store = transaction.objectStore(databaseName);
            store.put({value:value, query:query});

        })
    }

    //get value from database
    getQueryValueByID(query, callBack){
        //Our ID is query in idb 
        this._idbPromise.then(db => {
            return db.transaction(databaseName).objectStore(databaseName)
                    .get(query);
        }).then(object => callBack(null, object))
        .catch(error => callBack(error, null));
        
    }
}
currency converter offline web app
currency converter offline web app demo

Conclusion

We have completed our offline currency converter web application. The part which was emphasized on in this post is the application logic with Javascript. The full and well commented source code is available on Github here. You can play with the application, which I hosted on Github pages here.

If you liked this post, or it was useful to you, please 👍 like it, share it on twitter, facebook or other social media… in case you want to get updated on any new useful post, follow me on twitter,  Github  and like my page on facebook. And every other social media by clicking on the  buttons at the end of this post.

Follow me on social media and stay updated

Follow me on social media and stay updated

2 Replies to “Building An Offline Web App : Offline Currency Converter”

    1. This tutorial is just a demo, so I didn’t implement updating exchange rates.
      But, all you will need to do in the simplest scenario will be to query the API and update the already existing value in indexDB each time the user performs a conversion.

Leave a Reply

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