Image and Data Caching in Hybrid App

Swarnendu De May 26, 2016

In the present world, access to information is an essential need. We want to get hold of all the facts and factoids within an instant, irrespective of time or location. The most feasible ways to do these are via web and mobile apps.

Suppose you are searching for some medications in a medical store, or trying to book a hotel online , or perform some banking transactions, It would be of beneficial to have a mobile app supporting these necessary activities. But in a situation where the internet is not available, the app becomes useless. So just having the mobile app won’t be a solution. It would be useful if some of the static content or basic information of the app are accessible when you have gone offline.

In order to do so, we will ask the user, to sync the app time to time. On successful syncing, data will be stored into the device’s internal storage or SD card. It can be retrieved and used whenever it is necessary.

Let us explore how we can achieve offline syncing step by step using ionic framework.

Offline Syncing

In general, offline syncing can be of two types –

  1. Data Syncing
  2. Image Syncing

Offline Data Syncing

Data syncing can be done by storing the data and its assets from API into the browser’s local storage,after the user agrees to sync. There can be 3 prevalent cases:

  1. Downloading the overall data each time the user syncs the app and store them into the local storage.
  2. Removing data objects  from the local storage which are no longer sent from API.
  3. Downloading certain new data objects and appending them into the local storage along with the previous ones.

Syncing can be done once the user is online, to stay up-to-date with the current content. If some new data are inserted, we will be downloading and storing them into the local storage. If some data are removed from database, we will be removing it and its assets from local storage.

  1. Downloading the overall data each time and store them into the local storage:

This method is followed, if the data is subjected to frequent change from time to time. To let the user stay updated with the latest content, we will download the overall data each time the user decides to sync the app.

A field is created in the local storage where the data will be stored. Let us name it as ‘guide’.

angular.module(‘myApp’).constant('LOCAL_STORAGE_KEYS', {
  		guide: 'guides',
});

The data object to be stored (let us call it ‘guides’) is converted into JSON text and the JSON text is stored in string format.

window.localStorage[LOCAL_STORAGE_KEYS.guide] = JSON.stringify(guides);
  1. Removing data from the local storage which are no longer sent from the database:

In case the database gets modified and some previously used data were eliminated , we need to exclude those from the local storage. For such purpose there must be a primary key, with respect to which, the local storage objects will be compared. Let us consider “id” as primary key. Each guide will have an unique guide id associated with it.

We are introducing a new field called “sync” and marking it “true” for each matching id between the local storage and api data. Now the objects which does not have sync “true” are deleted and  the updated data is stored in the local storage again. Later the sync field is  removed.

var guides = window.localStorage[LOCAL_STORAGE_KEYS.guide]; 
        if (!guides) {
            guides = JSON.stringify({}); //creating an object notation
        }
	guides = JSON.parse(guides);
if (Object.keys(guides).length) {
	//For removing data that has been not send from API
	DataObject.forEach(function(eachGuide) {
                if (guides[eachGuide.id]) {
                    guides[eachGuide.id].sync = true;
                }
            });
	
	angular.forEach(guides, function(value, key) {
                if (value.sync) {
                    delete value.sync;
                } else {
                    delete guides[key];
       		}
            });
// The updated data is stored into the local storage again
window.localStorage[LOCAL_STORAGE_KEYS.guide] = JSON.stringify(guides);
}
  1. Downloading the new data from the database and storing it into local storage:

We need to download the new data as well, and store into the local storage.

a) First we store the id of the new data into an array called downloadedId.

var downloadedId = [ ];
angular.forEach(DataObject, function(valueApi, keyApi) {
   if (!guides[valueApi.id]) {
       downloadedId.push(valueApi.id);
            }
});

b) Now we call the API with respective id’s and store the resulting data into the local storage.

angular.forEach(downloadedId, function(value, key) {
    API call(value)
      .success(function(data) {
          guides[value] = data;
       })
       .error(function() {
         //error code                            
       })
        .finally(function() {
           window.localStorage[LOCAL_STORAGE_KEYS.guide] =JSON.stringify(guides);
        });

}); //for each ends

Offline Image Syncing

For image assets, they are stored in file system of the device. We need two cordova based plugins for this purpose. They can be installed as follows:

  1. Cordova-plugin-file: It is installed as cordova plugin add cordova-plugin-file
  2. Cordova-plugin-file-transfer: It is installed as cordova plugin add cordova-plugin-file-transfer

There are multiple valid locations to store persistent files on an Android device. To store it in phone memory we need to set preference in config.xml as

<preference name=”AndroidPersistentFileLocation” value=”Compatibility” />

For iOS, we store into the Library directory of the app. The preference is set as:
<preference name=”iosPersistentFileLocation” value=”Library” />

Creating file storage name

We need to create the folder name, in which the images will be stored. This can be your project name, or any other identification name for the folder you prefer. Let us go with the name ‘AppImages’

angular.module(‘myApp’).constant('LOCAL_STORAGE_KEYS', {
  	app: 'AppImages'
});

Storing in phone memory

We are using factory in angularjs to create the download image function.

var app = angular.module('app', [ ]);

app.factory('saveImage', function(LOCAL_STORAGE_KEYS, $q, $ionicPlatform) {
     return {
         downloadImage: function(url, fileName) {
             var deferred = $q.defer();
             window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function(fs) {
                     fs.root.getDirectory(LOCAL_STORAGE_KEYS.app, {
                             create: true
                         },
                         //success callback
                         function(dirEntry) {
                             dirEntry.getFile(fileName, {
                                     create: true,
                                     exclusive: false
                                 }, function(fe) {
                                     var p = fe.toURL(); //valid url path on device
                                     fe.remove();
                                     var ft = new FileTransfer();
                                     ft.download(encodeURI(url), p, function(entry) {
                                         if (entry && entry.toURL) {
                                             deferred.resolve(entry.toURL());
                                         } else {
                                             deferred.resolve();
                                         }
                                     }, function(err) {
                                         deferred.reject(err);
                                     }, false, null);
                                 },
                                 function() {
                                     deferred.reject(new Error('get file  failed'));
                                 }
                             );
                         });
                 },
                 function() {
                     deferred.reject(new Error('get directory failed'));
                 });

             return deferred.promise;
         }
     };
 });

Now, we need to call the downloadImage() function from our controller, in order to the receive the local URL of the image files. The actual URL’s need to be replaced by the local URL path, and then it is stored into the browser’s local storage. In offline session, along with textual data, local image URL will be fetched from the local storage and shown in our app.

saveImage.downloadImage(imageURL, imageName)
    .then(function(localURL) {
        var img;
	img = img.replace(‘actualURL’, ‘localURL’);
})

One constraint of using local storage is the limitation in storage space. It has maximum storage capacity of 15MB to 20MB. For storing large amount of data, local storage capacity will get exceeded. For more details on storage capacity of local storage you can visit the link increase local storage capacity. Also in case of storing audio or video files in phone’s internal memory or SD card, insufficient memory will fail to store such files.

Conclusion

We can have lot of benefits from offline storage like increased app performance by caching server data locally on device. Network usage can be limited everytime user uses the app.Hence your ultimate aim for building a robust app, becomes successful.

I have recently used this technology for my latest ionic app, and it works like a charm. Hope this helps you in your app development process as well.

Feel free to provide suggestions and queries in form of comments below.