Using IndexedDB to work with images offline

This tutorial describes how to save a set of images from Appery.io Files collection
into IndexedDB cache with making them available when the app is in offline mode.

Introduction

This Ionic tutorial demonstrates how to use IndexedDB Storage and Login-Registration App plugins to build an application in Appery.io that will get user images from the Files collection and make them available when the device is in offline mode. This app can be published either as a regular iOS and/or Android app or as a PWA website.

🚧

Important Note!

Note that the option of creating new apps with the Ionic framework was removed but we still support the projects that were created with it earlier.

App pages

1250

App pages

This app consists of 3 pages. When started online, it asks for login and goes to the Welcome page, then retrieves the images from the Files collection and saves them into IndexedDB. If started in offline mode, it goes directly to the Gallery page where images are retrieved from IndexedDB and displayed.
This app can be published either as a regular iOS and/or Android app or as a PWA website.
The sample project backup can be found here.

Database structure

Two standard collections Users and Files store user credentials and binary image data.
In this example, users are associated with their own sets of pictures. For that purpose, we will first create a custom userfiles collection to maintain the connections between the users and their images.

183

You can download the sample database file here and then import it into Appery.io by clicking
Create new database from the Databases tab and then import it from backup with setting Gallery as its name and choosing the downloaded backup file.
The passwords for all users in the sample Users collections ( rock, classic, or jazz) are set to 111.

869

userfiles collection

Login page

Create a new Ionic app from the Blank template and import the Login-Registration App plugin into it: Create New > From Plugin > Login-Registration App.
On the plugin settings popup, select:
Keep current for Default routing
Keep current index page for Index screen
The Login page is almost ready, but the User Registration scenario is not needed, so click the Outline tab and delete the Card2 component.
You should get the following outline structure:

207

Login page outline

Now, let’s import the database services into the project. For real projects, we recommend wrapping a database REST API into server-codes, but since we are demonstrating the IndexedDB service here, direct database REST services are the fastest way to go.

  1. Click Create New > Database Service, select the Gallery database and import the Login service, the Query service for the userfiles collection and the Read service for the _files collection.
  2. Switch to the Scope tab and inside the login function, replace the service name LoginUser_service with Gallery_login_service so that the code looks like this:
Apperyio.get("Gallery_login_service")(requestData).then(
...
  1. Now, update the request mappings for this service:
798

dataStorage service

Together with the Login-Registration App plugin, the dataStorage AngularJS service has been imported to be used for sharing data between the app pages. It can be found in the JavaScript section of the left-side menu in the visual editor.

  1. The first things to be saved after login in most apps are sessionToken and userId variables. sessionToken is used to access secure APIs and userId will be reused on many pages to identify users.
  2. Replace the body of the Func function in the dataStorage service with:
function Func( Apperyio ){
  var ds = this;
  ds.sessionToken = "";
  ds.userId = "";
}
  1. Modify the Gallery_login_service response by adding:
userData.sessionToken = success.data.sessionToken;
userData.userId = success.data._id;

As a result, this response should look like this:

433

login_service response

Welcome page

  1. The existing Card1 component on the Welcome page can be deleted.
  2. Drop a new Card component on the page and add an Image component to the Item text. The Card header can be deleted.
  3. Also, drop a Button component under the card, set its Text attribute to Offline Gallery.
    We should get the following outline structure:
206

Welcome page outline

List of user files

First, locate the list of user files from the userfiles collection:

  1. Add sessionToken and userId variables to the SCOPE tab of the Welcome page, set their type to String.
  2. Replace the init method of the Welcome page with the following code:
var userData = Apperyio.get("dataStorage");
$scope.sessionToken = userData.sessionToken;
$scope.userId = userData.userId;
  1. Drop the Gallery_userfiles_query_service to the end of the init request method (Insert snippet > Invoke service).
  2. Map sessionToken to the X-Appery-Session-Token header.
  3. Map userId to the where parameter of the query. Click the JS button next to the parameter and update it to be:
return { 'user._id': value };

As a result, the query will return the list of images from the userfiles collection for the given user.
The final mapping should look like this:

825

userfiles mapping

Reading image data

  1. Add the following code to success callback of the Gallery_userfiles_query_service:
success.data.forEach(function(userfile) {
	$scope.readImageData(userfile);
});

Here, the readImageData function for each of the results is called.
2. Create a new function, readImageData and add a userfile parameter to it (under Arguments*).
3. Drop the Gallery__files_read_service into readImageData (Insert snippet > Invoke service).
4. Map sessionToken to X-Appery-Session-Token header.
5. Add the following code after the autogenerated request function:

requestData.params = {};
requestData.params.file_name = userfile.image.fileName;
requestData.params.encoded = "base64";

It will add the image file name to the request and specify that image data should be returned in Base64 format.
Finally, the service request should look like this:

573

Displaying images

Let's create some array to store the results of Gallery__files_read_service.

  1. Go to the Model section in the left-side menu and add ImageList model of the Array type with an element of the Object type.
    The final project model should look like this:
1055

ImageList model

  1. Create a new imageList variable under the SCOPE tab of the Welcome page and assign ImageList type to it.
  2. Add the following code to the beginning of the readImageData function:
$scope.imageList = [];
  1. Add the following code to the success callback of the Gallery__files_read_service:
var imageData = success.data;
$scope.imageList.push({
  name: userfile.image.originalFileName,
  data: imageData
});
  1. On the DESIGN tab of the Welcome page, select the Card component and add ng-repeat attribute to it with the value img in imageList | orderBy: 'name'
  2. Select the Image component and set ng-src attribute to data:image/png;base64,{{img.data}}

Intermediate testing

At this stage, you should be able to test your app.
Before testing, check under Project > Routing that the default route is set to the Login page.
Log in with username jaz** and password 111* to see the list of images for this user.

Gallery page

Now, the images from the Files collection are displayed on the page.

  1. Let’s create one more page named Gallery to display images previously stored in IndexedDB. The default template Blank Ionic page can be accepted.
  2. Drop a new Card component on the page, and add an Image component to the Item text. The card Header can be deleted.

Adding IndexedDB Storage plugin

Now, we have to add the IndexedDB Storage plugin to the project.

  1. Click Create New > From Plugin, then select IndexedDB Storage and click Import selected plugin. Leave Keep current for default routing in plugin settings popup and click Apply settings.
  2. The indexedDbStorage AngularJS service will be added to the project providing functions to access IndexedDB API.

Open database

Add the following code to init method on the Login page:

Apperyio.get("indexedDbStorage").open("OfflineDB",1,["Gallery"]).then(function() {
  if (!navigator.onLine) {
  	Apperyio.navigateTo("Gallery");
  }
});

Here, we are opening OfflineDB and assigning it version 1. If later we decide to upgrade our database and use some different version number, then all our object stores will be deleted and recreated to the empty state so, in most cases, it is recommended that this version parameter be kept unchanged.
The array in the last argument is the list of object stores what is similar to the list of collections in ApperyDB. Now, let's use the Gallery object store which will be created in an empty state if it does not exist.
After this open operation is completed, we’ll check if a mobile device is currently offline, and if so, navigate directly to the Gallery page.

Storing images into IndexedDB

Add the following code to the end of the success callback of Gallery__files_read_service (this is readImageData function of the Welcome page):

Apperyio.get("indexedDbStorage").put('Gallery', atob(imageData),
userfile.image.originalFileName);

Finally, this success callback should look like this:

759

Getting images from IndexedDB

  1. Add the following code to the init method of the Gallery page:
$scope.imageList = [];
Apperyio.get("indexedDbStorage").list('Gallery').then(function(images) {
  images.forEach(function(img, index) {
    $scope.imageList.push({
      name: img.key,
      data: btoa(img.value)
    });
  });
});
  1. On the DESIGN tab of the Gallery page, select the Card component and add ng-repeat attribute to it with the value img in imageList | orderBy: 'name'.
  2. Select the Image component and set its ng-src attribute to data:image/png;base64,{{img.data}}.
  3. Add navigate-to attribute to the Offline Gallery button on the Welcome page and set it to Gallery.

Final testing

You can export the app to a mobile device (Export > Binary (.ipa) or Export > Binary (.apk)) and start it in online mode, then log in and make sure that images are available on the Welcome page.
To start the app in offline mode, turn the Internet connection off and close the app instance running on the device. Then, restart the app. You should be able to see the images cached in IndexedDB.

This app can also work as a PWA. Enable PWA publishing on the PWA tab under App settings in your project.
Close the project and choose some name at the Hosting section for the Appery.io domain and then click Publish. If this name has not already been taken, after the successful publishing process you should be able to open https://<your_app_name>.app.appery.io URL in your mobile browser, add it to the Home screen, and start it in offline mode the same way you did for a regular iOS or Android app.

IndexedDB Storage plugin API

IndexedDB is storage in a browser that allows caching some data on mobile phones for offline usage or to speed things up.
The list of functions provided by the IndexedDB Storage plugin (all functions return AngularJS promises) can be found below:

open(dbname, dbversion, dbstores)

Opens database.

ParameterDescription
dbnameDatabase name
dbversionDatabase version
dbstoresArray of object stores

add(store, item, key)

This is for adding new records to an object store.

ParameterDescription
storeObject store name
itemThe item to be stored
keyThe key to use to identify the record

The detailed description can be found here.

put(store, item, key)

Updates a given record in a database or inserts a new record if the given item does not already exist.

ParameterDescription
storeObject store name
itemThe item you wish to update (or insert)
keyThe primary key of the record you want to update

More information can be found here.

get(store, key)

This is for retrieving specific records from an object store.

ParameterDescription
storeObject store name
keyThe key to use to identify the record

The detailed description can be found here.

list(store)

Lists objects in store.

ParameterDescription
storeObject store name

Returns a list of (key, value) pairs.

remove(store, key)

Deletes the specified record.

ParameterDescription
storeObject store name
keyThe key to use to identify the record

For more details, check here.

clear(store)

This is for deleting all the current data out of an object store.

ParameterDescription
storeObject store name

More details can be found here.