The Official Ionic Blog

Build amazing native and progressive web apps with HTML5

Special thanks to Ionic’s Justin Willis for updating this repo and post on November 4, 2016.

Service workers, app shell, progressive web apps—anything involving offline support seems to be all the rage these days. Whether it’s a new design concept or a shiny new browser feature, developers seem to have many options when it comes to adding better offline support to their apps. All of these options can be overwhelming, but it doesn’t need to be that way.

Start with the Basics

While service workers and the app shell design concept are great, sometimes it’s best to start with the basics. SQLite is a mature database that has been around for ages. Ionic has a great native plugin for it, which happens to work great on iOS, Android, and Windows Phone.

In this series, we’ll be building a weather app that is fully functional while offline. This post will walk through the steps of setting up the app and adding SQLite, so it can display data while offline. The app will pull seven days of weather data from a service and store it locally for later use. If a user opens the app without an internet connection, the app can fall back to use data collected up to seven days ago. Here’s what the final product will look like:

weather app

Getting Started

The code for this weather app can be found here. We will be working from the part-1 branch, which contains a number of steps that are commented out. You can follow along by uncommenting the code in each step of this blog post. Let’s begin by cloning the repo and installing its dependencies:

$ git clone https://github.com/jgw96/ionic-offline-demo.git
$ cd ionic-offline-demo && git checkout part-1
$ npm install

We’ll also need to add Ionic’s SQLite and Geolocation plugins:

$ ionic plugin add cordova-sqlite-storage
$ ionic plugin add cordova-plugin-geolocation

Finally, if you’re planning on running the demo locally, you’ll need to sign up and obtain API keys for forecast.io and Google Maps. We’ll be using these services to get weather data and translate our geolocation into friendly city and state names.

Using SqlStorage

We’ll be using SQLite Ionic Native module, a wrapper Ionic provides that makes it easier to interact with a SQLite database. Let’s open up home.ts and import it:

// [Step 1 - Using SQLite]
Import { SQLite } from ‘ionic-native’;

Creating Tables

Next, we need to create a table to store the forecast data we’ll be saving later. Each row in this table will represent data for a particular day. When our app is initialized, or in the constructor method for our page, let’s add the following query to create the table:

    // [Step 2 - Creating Tables]
    this.storage = new SQLite();
    this.storage.openDatabase({
      name: ‘ionic.offline’,
      location: ‘default’
    }).then(() => {
      this.storage.executeSql(`create table if not exists forecasts(
        date CHAR(5) PRIMARY KEY,
        location CHAR(40),
        icon CHAR(30),
        tempCurrent INT,
        tempMin INT,
        tempMax INT
      ))`, {});
    });
   ```


### Saving Data
Now that our table is created, let’s write a function that will save forecast data. We will pass this function a `forecasts` object, which contains data obtained through the [forecast.io API](https://forecast.io/):


```ts
  // [Step 3 - Saving Data]
  saveForecasts = (forecasts) => {
    let query = "INSERT OR REPLACE INTO forecasts VALUES (?, ?, ?, ?, ?, ?)";
    for (let forecast of forecasts) {
      this.storage.executeSql(query, { forecast.date,
                                 forecast.location,
                                 forecast.icon,
                                 forecast.tempCurrent,
                                 forecast.tempMin,
                                 forecast.tempMax });
    }
    return forecasts;
  }


Retrieving Data

Our weather app needs to be able to retrieve the forecast for the current day. The following function will return forecast data for the given date:

  // [Step 4 - Retrieving Data]
  getForecast(date: string) {
    return this.storage.executeSql("SELECT * FROM forecasts WHERE date = ?", { date }).then((resp) => {
      if (resp.res.rows.length > 0) {
        for (var i = 0; i < resp.res.rows.length; i++) {
          let item = resp.res.rows.item(i);
          return item;
        }
      }
    });
  }

Tying it Together

When the app is initialized, we now have the option to use data stored locally. If we can’t retrieve local data, we can go to the network to retrieve the data. Let’s check out what this looks like in code:

    // [Step 5 - Tying it Together]
    this.getForecast(this.getToday()).then((data) => {
      if (data) {
        // obtained forecast from database
        this.data = data;
      } else {
        // could not get forecast from database, go to network
        this.fetchForecasts();
      }
    });

With this added, our app will be able to display today’s forecast while offline, assuming the forecast was saved earlier. Check out the GitHub repo for all of the code used to build this app.

Not Just About Offline Support

By retrieving data from a local SQLite database, rather than going to the network, we’re improving the experience for all of our users, not just the ones using our app offline. Network requests are expensive, and by preventing them, our content gets displayed more quickly. Utilizing local storage is not just about offline support; it’s about performance.

Conclusion

Adding offline support to an existing app can be straightforward when you start with the basics. By utilizing the Ionic Native SQLite module, we not only add better offline support to our apps, we improve their performance.

At this point, you may be wondering what happens when we can’t retrieve data from the database or from the network. In the next post, we’ll address this by adding a No Connection page that gives the user an option to open their network settings.

Check out the code on GitHub, and let us know how it goes!

  • Young Park

    if it’s just a simple cache that’s needed, i’d better use PouchDB.

  • Sergio Milici

    How do you use service workers on a cordova app if they only run over https and not under file:// protocol?

    • https://twitter.com/drewrygh Drew Rygh

      I think you’ve answered your own question — you don’t 🙂

      There is a workaround on iOS, and a plugin, but AFAIK there is no way to use service workers in an Android WebView right now. I’d imagine this will change eventually.

      Plenty of other ways to add offline support though 🙂

      • Sergio Milici

        Yes, i found that plugin. Thanks.
        I was more interested in offline support with sync capabilities. Just keeping the data locally are many options to do that, for for syncing not. Maybe firebase but on web still is not 100% real offline. Maybe couchdb too, but you have to have a server to sync with and there are not too many paas that offers that. i’m working with crdt but it’s a real different scenario. I want 100% offline support and sync without special tricks.

        Great article and thanks for the response.
        if you have any thoughts about this, i would like to continue talking with you.

        • jerome

          I use a ‘homemade’ solution with cordova content-sync. But for that i use Json persistence with the cordova file plugin not sqlStorage.
          On the server side I have a service that ‘transforms’ the parts i need from my MYSQL data into a downloadable zip File containing a JSON file and referenced images/files). This is important for cordova-content-sync : it works with a download link and zip files only. If you need to constantly check for sync (..and you don’t have any heavy stuffs you need to sync )use a simple REST API.

          One you have your JSON you can do pretty powerful stuff with lokiJs/underscore etc. if you need good optimization/performance. I did some test with LokiJs and it’s quite great 🙂 but for my app I didn’t need that kind of stuff, just a simple json and underscore.

          you just have to create an algorithm that would define when you need to sync (I use a last_updated_at attribute) sent via a rest api, if the last_updated at is different from the local last_updated_at i download the zip file and cordova-content-sync replaces my files.

          I know pouchDb/firebase have powerful sync abilities but in my case I had a MySQL database and I had to adapt to that :).

        • Jose

          I’m working on a mobile app that needs to support offline mode in case of rough internet connectivity and sync the data with a RestFull API every time that the internet is up and I still running into a few issues to sync the data. I didn’t find any good thoughts or ideas on how to manage synchronization in real time, if you have gotten any ideas or is interested in go deeper to figure it out, please send me a message.

  • Graphefruit

    Any conclusion about image/file caching and not just data?

  • http://www.raymondcamdencom/ Raymond Camden

    Small nit:
    “We’ll also need to add Ionic’s SQLite and Geolocation plugins:”
    Those aren’t by Ionic, right?

    • https://twitter.com/drewrygh Drew Rygh

      You’re right, I’ll fix that up, thanks!

  • Rolf Erikson
  • KingOfMyRoom

    this.storage = new Storage(SqlStorage);

    This first line gives me a promise error, I am not able to use SqlStorage

    EXCEPTION: Error: Uncaught (in promise): EXCEPTION: Error in ./HomePage class HomePage_Host – inline template:0:0
    ORIGINAL EXCEPTION: TypeError: win.openDatabase is not a function

  • http://www.yuricamara.com.br/ Yuri Camara

    And about PouchDB?

    • Jose

      as far I’m aware you have to have a couchDB in the backend to be able to use pouchDB.

      https://pouchdb.com/faq.html

      Can PouchDB sync with MongoDB/MySQL/my current non-CouchDB database?
      No, your backend needs to speak the CouchDB replication protocol. The magic of PouchDB CouchDB sync comes from this design, which in particular requires all documents to be versioned with the _rev marker. This allows PouchDB and CouchDB to elegantly handle conflicts, among other benefits.

  • disqus_9pSTO2tt0E

    Weather can change so how will you know the forcast in local database isn’t out of date?

  • Fabricio Avancini

    I get this error: “Supplied parameters do not match any signature of call target” at the line “this.storage = new SQLite();”. Anyone knows what may be happening?

  • Muhammad

    How would SqlStorage work with JSON or NoSql? Say the model is far bigger than simple weather data, how do we go about storing it this way? Whats the best practice?