Offline-first Mobile Apps Using Cordova Hybrid Platform

Mobile applications should be designed intelligently keeping into consideration the needs and expectations of the target audience. And while developing mobile applications, our focus should always be on creating apps that are faster, smarter, and deliver the right results. In order to do so, a mobile app should be able to perform tasks with minimal clicks, be highly intuitive and offer offline support at the same time.

In this blog, we will focus on building offline-first mobile apps using the popular hybrid platform Cordova.

Offline-first Mobile Apps

Offline-first mobile apps have more use cases than the ones that are connected. Although, building an offline-first mobile app is still progressing slowly, but the benefits of working seamlessly even when the WiFi is spotty or the bandwidth is low can’t be ignored. While developing apps, developers should focus on providing an offline-first feature that lets users interact with the app even when there’s no reliable connectivity.

Many enterprises have already developed offline-first mobile apps and Google is one of them. Google Maps as you know is one of the most reliable navigation service application in the market. But, with a weak internet connection, even Google Maps struggles to fetch accurate information. To resolve the issue, Google has come up with a pair ‘offline’ feature which lets you download the route even when the internet connectivity is disabled or spotty. It has features to save specific areas from the map including navigation services directly to your mobile phone or tablet and display content whenever requested.

Enterprise Mobile Apps Using Google Flutter

Offline-first Architecture

We cannot show remote data when there is no internet connectivity. Hence, we will store the remote data to the device storage. When the internet is not available, we pull this data from internal storage and display it to the user. This is called an offline mechanism.

Following diagram depicts the architecture of offline functionality in a mobile app.

Offline-first Mobile Apps Architecture Diagram
Types of Data Storage in Hybrid Mobile Apps

Here are the 6 ways to store data in an application:

  • Session Storage: It is used for temporary storage and gets cleared as soon as the application is closed. It uses a key-value pair.
  • Local Storage: It is used for persistent storage and uses a key-value pair. It can store up to 5MB of data.
  • Cookies: Cookies are often used as storage type on the web. It also uses a key-value pair.
  • Web SQL: Web SQL is web SQLite. It allows up to 5MB max data storage. Only Android and iOS have WebSQL, whereas Windows has Index DB.
  • Native SQLite Storage: It is a plugin that will point to native SQLite of the respective platform. It is easy to maintain, lightweight, and flexible. It can store unlimited data.
  • Index DB: It is a sequential database of windows phone platform. It is not easily maintainable like SQLite. It also uses key-value pair storage.

Let’s explore how to build an offline mode mobile app using the popular Hybrid Platform Cordova.

Install Following Prerequisites
  • Java Development Kit 8 (JDK 8)
  • Android SDK for Android build, XCode for iOS build
  • NodeJS npm for CLI operations like project creation, plugins and build generations.
Create New Project using NPM
  • Install Cordova using NPM
    • Windows : $ npm install -g cordova
    • OS X and Linux : $ sudo npm install -g cordova
  • Create a Project
    • Windows: $ cordova create foldername com.CordovaExample.OfflineSample Projectname
    • OS X and Linux : $ sudo cordova create foldername com.CordovaExample.OfflineSample Projectname
  • Create Platforms
    • Windows
      • $ cd foldername
      • $ cordova platform add android
      • $ cordova platform add ios
    • OS X and Linux
      • $ sudo cd foldername
      • $ sudo cordova platform add android
      • $ sudo cordova platform add ios
Design Patterns Required

Here are a few points to be noted:

  • We use design patterns that are well-structured, maintainable and preserve code extensibility.
  • The selection of a design pattern depends on the type of requirement.
  • The four popular design patterns are Prototype, Module, Observer, and Singleton.
  • In Hybrid app, a prototype is the most commonly used design pattern, which we will use in the demo. It obeys OOPS concepts. An object created for constructor will have the prototype object.

Let’s create an object for App()

//constructor
var App = function(){
var index = 0, dataLength = 0;
};
var app = new App(); // Creation object

If we console app object, prototype property will have constructor property. The constructor will again point to the prototype object as below:

{constructor: ƒ}
constructor: ƒ ()
arguments: null
caller: null
length: 0
name: "App"
prototype: {constructor: ƒ}
__proto__: ƒ ()
[[FunctionLocation]]: VM45:1
[[Scopes]]: Scopes[1]
__proto__: Object

Let’s add another function using the prototype to the App constructor:

App.prototype.initialize = function(){
document.addEventListener('deviceready', this.onDeviceReady.bind(this), false);
};

Above method will be cloned to the prototype using prototype property, like shown below. We can also extend the number of methods.

Prototype Property

Any prototype methods can be called from other methods using ‘this’ keyword.

Offline Implementation

Let’s create an offline demo using a prototype design pattern with the Cordova framework. We will list down the items with download icon on the right side. The common code will run on a browser without Cordova and on any mobile OS with Cordova. On a mobile, a user can save the item by clicking on the download button in the SQLite using the native plugin.

For the UI in Hybrid app, add required CSS, JavaScript libraries to the project in index.html

Add CSS within the head tag


And add JavaScript files at the end of the file:




 //Remove if you are running on web

For security reasons, it will not allow third-party URLs.

Below is the default meta tag.

<meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *; img-src 'self' data: content:;">

In order to add third-party images in the list, change the security policy as shown below.

<meta http-equiv="Content-Security-Policy" content="default-src * gap: data: blob: 'unsafe-inline' 'unsafe-eval' ws: wss:; connect-src * data: blob: ws: wss:;">

Check when the document is ready to download.

var app;
// Triggered when DOM loaded
$(document).ready(function() {
console.log("ready!");
platformname = $('html').attr('platform');
app = new App();
if (platformname == 'web') // if web
app.onDeviceReady();
else
app.initialize();
});

To check whether the code is running on web or Android, we have to manually add platform attribute to the HTML tag and initialize the method.

<html platform="android">
App.prototype.initialize = function(){
document.addEventListener('deviceready', this.onDeviceReady.bind(this), false);
};

Check for internet connection

document.addEventListener("offline", this.offline.bind(this), false);
document.addEventListener("online", this.online.bind(this), false);

These listeners will call the mobile native internet connectivity code. So, we have to add below native plugins.

To store data in SQLite DB, add the plugins first.

cordova plugin add cordova-plugin-network-information
cordova plugin add cordova-sqlite-storage

We are going to show the status of the internet connection as a subheader below the navigation header. So that developers will understand which data is actually getting displayed to the end users.

App.prototype.offline = function() {
$('#internet_div').css('background-color', 'rgba(189, 33, 48, 0.35)'); //Show status
$('#internet_div p').text('Internet connection is unavailable');
};
App.prototype.online = function() {
$('#internet_div').css('background-color', 'rgba(40, 167, 69, 0.39)'); // Show status
$('#internet_div p').text('Internet connection is available');
app.index = 0;
app.loadListPage();
};

If the internet is available, loadListPage() method will be called else loadOfflineListPage() method will be called.

App.prototype.initiateListCall = function() {
if (checkConnection())
this.loadListPage();
else {
this.offline();
this.loadOfflineListPage();
}
};
App.prototype.repeatuntilallload = function() {
app.index++;
if (app.index < app.dataLength)
this.loadListPage();
else
this.addEvents();
};
App.prototype.loadListPage = function() {
var srcdownload;
checkifexists(data_arr[app.index], function(b) {
if (!b)
srcdownload = “img/download.png”;
else
srcdownload = “img/downloadhighlite.png”;
var data = ‘<ul class=”listgroup”><li onclick=”app.movenext(‘+app.index+’,true);”><div class=”row “><div class=”col-4 col-sm-3 col-md-2″>’ + ‘<img src=”‘ + data_arr[app.index].img + ‘” width=”100″ height=”100″ class=”mar10″>’ + ‘</div>’ + ‘<div class=”col-6 col-sm-7 col-md-4 paddingLeft0 rel”><div class=”abscenter”>’ + ‘<h4 class=”marg0 headerText”>Item’ + (app.index + 1) + ‘</h4>’ + ‘<p class=”hinttext”>Lorem Ipsum is simply dummy text of the printing and typesetting industry</p>’ + ‘</div></div>’ + ‘<div class=”col-2 col-sm-2 col-md-6 tac downloadImages”>’ + ‘<img src=’ + srcdownload + ‘ width=”40″ height=”40″ class=”downloadImage margt40″ id=”imagedown’+app.index+'” clickattr=’+ app.index+’>’ + ‘</div>’ + ‘</div></li></ul>’;
$(‘#listview1’).append(data);
app.repeatuntilallload();
});
};

read() method will fetch the offline data from the SQLite database. In this demo, we are using static array data as shown below.

var data_arr = [{text: “Item1″,img:””}]
App.prototype.loadOfflineListPage = function() {
read(function(rowArray) {
if (rowArray.length == 0)
alert(“Offline data not found”);
offlineArray = rowArray;
for (var i = 0; i < rowArray.length; i++) {
var data = ‘<ul class=”listgroup”><li onclick=”app.movenext(‘+app.index+’,false);”><div class=”row “><div class=”col-4 col-sm-2″>’ + ‘<img src=”‘ + rowArray.item(i).img + ‘” width=”100″ height=”100″ class=”mar10″>’ + ‘</div>’ + ‘<div class=”col-7 col-sm-10 paddingLeft0 rel”><div class=”abscenter”>’ + ‘<h4 class=”marg0 headerText”>Item’ + (i + 1) + ‘</h4>’ + ‘<p class=”hinttext”>Lorem Ipsum is simply dummy text of the printing and typesetting industry</p>’ + ‘</div></div></div></li></ul>’;
$(‘#listview1’).append(data);
}
});
stopLoader();
};

If we’re creating listview dynamically, we have to add Events to download file icon to find out which item has to be downloaded into the database.

//Add Events to the dynamic list
App.prototype.addEvents = function() {
$(‘.downloadImage’).off(‘click’);
$(‘.downloadImage’).on(‘click’,function(event ){
event.stopPropagation();
app.downloadImagesFromUrl($(this).attr(‘clickattr’));
});
}

We must remove the event listener when we are adding listener dynamically to any HTML element. Otherwise, the event may trigger more than once.

SQLite CRUD Operations

We have to open the database using a database name with the location of the database file. First, we will check the existing plugin in the app to open the database. If the creation of a database fails, an error popup will be displayed, else we will perform CRUD operations.

//create DB
function openDatabase(callback) {
if (!window.sqlitePlugin) {
callback(true);
} else {
db = window.sqlitePlugin.openDatabase(
// options
{
name: “ImageCache.db”,
location: 1 // for iOS (0=Documents (default, visible in iTunes, backed up by iCloud), 1=Library (not visible in iTunes, backed up by iCloud, 2=Library/LocalDatabase (not visible in iTunes, not backed up by iCloud))
},
// success callback
function(msg) {
console.log(“success——–>: ” + msg);
callback(true);
},
// error callback
function(msg) {
console.log(“error: ” + msg);
alert(“oops! Something went wrong”);
}
);
}
}
//Insert in to DB
function insert(imageurl,_text,imgblob){
db.transaction(function(tx) {
tx.executeSql(“CREATE TABLE IF NOT EXISTS listdata (id integer primary key,imageurl text, img text,data text)”);
tx.executeSql(
“INSERT INTO listdata (imageurl,img,data) VALUES (?,?,?)”,
[imageurl,imgblob,_text],
function(tx, res) {
console.log(“insertId: ” + res.insertId + “, rows affected: ” + res.rowsAffected);
},
function(tx, res) {
console.log(‘error: ‘ + res.message);
});
});
}
// Verify the item before insert in DB. Removes duplication
function checkifexists(imgurl, callback) {
if (!window.sqlitePlugin) {
callback(false);
} else {
console.log(imgurl);
db.transaction(function(tx) {
tx.executeSql(
“select * from listdata where imageurl=?;”, [imgurl],
function(tx, res) {
// alert(“rows: ” + res.rows);
if (res.rows.length > 0)
callback(true);
else
callback(false);
},
function(tx, res) {
console.log(‘error: ‘ + res.message);
callback(false);
});
});
}
}

We have image URLs which will automatically fetch the image byte array and show image tag SRC attribute. But in an offline mode, we just cannot store the image URL, hence we convert the image URL to base64 by using the below method.

//Convert image url to base 64
App.prototype.downloadImagesFromUrl = function(index) {
if (platformname == ‘web’) { //web platform
alert(“Cannot save data in to sqlite on web “);
return;
}
if (!checkConnection()) { // internet is unavailable
alert(“Cannot download as internet not available”);
return;
}
checkifexists(data_arr[index], function(b) {
if (!b) { // if not exists
toDataUrl(data_arr[index], function(myBase64) {
//console.log(myBase64); // myBase64 is the base64 string
stopLoader();
alert(“saved on device”);
$(‘#imagedown’+index).src(‘img/downloadhighlite.png’)
});
} else // base64 already exists in the database
alert(“Already exists”);
});
};
Use Cases to Test
  • On launching the app, the user will see the status of internet connection just below the header as “Internet is not available” when WiFi is unavailable.
  • If any offline data exists in the local storage, the user will see a list of offline data/image/status.
  • If the user clicks on any one of the items, it will navigate to the next page and display the information about that item (even when the connection is unavailable).
  • If no data exists, it will display ‘Offline data is not available.’
  • When internet connectivity is available, automatically the offline data will be queued to be uploaded via an API. This process happens seamlessly in the background.
Offline Mode Advantages
  • Offline storage is useful when there is a large number of data/images/videos to be called from API. Usually, it takes a lot of time to load heavy data from the server. So, we will call the API once, store the data in the local storage and display it to the user as and when required. The local database would be altered when changes take place in the server.
  • SQLite is lightweight, simple and faster than file operations.
  • In remote areas, where there is a lack of internet connectivity, offline mode helps in business continuity for field persons.
  • Offline-first mobile apps are extremely beneficial for e-commerce applications, offline maps, pre-sales, and product configuration applications.
Disadvantages
  • When offline, the data displayed to the user may not be up to date.
  • Although there are no limitations on max data storage in SQLite, it is recommended to keep the process simple and flexible. Storage depends on the phone’s storage capacity.
  • To implement an offline mode, mobile app code changes have to made in both API and the app to add queuing, while syncing support.

Through this blog post, I have tried to exhibit how Offline-first Mobile Apps can be used to build compelling applications that can work seamlessly, even when there is low to no internet connectivity. Which eventually will lead to better user experiences even in challenging network connectivity scenarios such as being stuck in a train tunnel, having to rely on over-crowded conference Wi-Fi, or passing through a ‘No-Network’ zone. And it’s now safe to say that apps working on offline mode ensure a leg up on the competition, and greater customer loyalty.

Evoke’s Hybrid App Development Services

The hybrid app development experts at Evoke understand the growing needs and complexity of a modern mobile enterprise and build reliable and robust mobile applications that work seamlessly across all platforms. Our innovative and feature-rich hybrid mobile apps enable our clients to reduce costs and drive maximum return on investment. Our unparalleled hybrid app development, maintenance, and support services ensure optimized performance of mobile apps across devices. To learn more about Evoke’s hybrid app development services, please talk to us at +1 (937) 660-4923 or fill our online form.