Using the Capacitor Storage API for Storing Data

Using the Capacitor Storage API for Storing Data

Follow Josh Morony on

When building our applications, we will often want to store data in some kind of storage mechanism such that it is available to use even after the application has been exited and returned to later. A common way to do this for web-based applications is to make use of localStorage which is available through the global window object. For example, storing a bit of data in local storage is as simple as doing this in a browser environment:

window.localStorage.setItem("username", "josh")

This will store data in the browser’s local storage, but there are some drawbacks to this - mostly the potential for the data to be wiped by the operating system in order to free up space (although this is uncommon). When running an application natively (e.g. if we have created a native build for iOS or Android) we have additional native storage options that we can make use of which are more stable.

If you have a background with building Ionic applications with Angular, then you may have used the IonicStorageModule at some point. This can be used in Angular applications to automatically utilise the best storage option available, depending on the environment that the application is running in.

In this tutorial, we will be covering something similar, except we will be using the Storage API that is provided by default with Capacitor. The Capacitor Storage API will store data in (and retrieve data from) different locations depending on the environment the application is running in. The storage mechanisms utilised are:

Throughout this tutorial and an associated video I filmed, we are also going to use this as a chance to get a little exposure to how Capacitor plugins work. We will discuss how we can use the same Storage API in our code even though the underlying implementation for each platform differs. We will not be discussing this in a great deal of detail, nor will be walking through how to actually create a custom plugin, but the idea is just to expose you to the idea a little bit.

It is worth noting that this is not an all-in-one solution for storage. Although you can store complex data like arrays and objects (e.g. storing an array of todos would be fine), it is still just a simple key-value storage system. This means that you provide a key and then you can get whatever value is stored on that key. If your application has more complex requirements, like the need to query data, you might require a different storage solution.

Even though data stored in local storage is unlikely to be wiped by the operating system or otherwise lost, I generally prefer only to use local storage (even native local storage) for “unimportant” data that can be lost (e.g. preferences, cached data). For data that is important, I prefer to use a solution where data can be restored if necessary (e.g. a remote database like CouchDB, MongoDB, or Firestore). This is, of course, up for you to decide what best suits your needs.

Using the Capacitor Storage API

In this example, we are going to create a service using Ionic and StencilJS that will just export a few functions that will allow us to easily interact with the Storage API. Although this example is specifically for StencilJS, the same basic concepts can be utilised in other scenarios as well - as long as you have Capacitor installed.

For now, we are just going to focus on implementing and using the service, and then in the following section we will discuss the underlying implementation details.

Create a file at src/services/storage.ts that reflects the following:

import { Plugins } from "@capacitor/core";

const { Storage } = Plugins;

export async function set(key: string, value: any): Promise<void> {
  await Storage.set({
    key: key,
    value: JSON.stringify(value)
  });
}

export async function get(key: string): Promise<any> {
  const item = await Storage.get({ key: key });
  return JSON.parse(item.value);
}

export async function remove(key: string): Promise<void> {
  await Storage.remove({
    key: key
  });
}

In this example, we are creating and exporting three functions: set, get, and remove. The main reasons that we are doing this instead of just using Storage from Capacitor directly wherever we require are:

  • All of the implementation details are in this file, and our application doesn’t need to worry about how data is being stored. It can just call get, set, or remove
  • We only need to import the Storage API from Capacitor in one place
  • We can only store string values in storage, and these functions will automatically handle using JSON.stringify to convert arrays/objects into JSON strings and then convert them back again using JSON.parse when we retrieve the values

The Capacitor Storage API is simple enough already, but we have made our job even easier by creating these helper functions. To use those functions, we would then just import them somewhere like this:

import { Component, State, h } from "@stencil/core";
import { get, set } from "../../services/storage";

@Component({
  tag: "app-home",
  styleUrl: "app-home.css"
})
export class AppHome {
  @State() testValue: string;

  async componentDidLoad() {
    this.testValue = await get("myValue");
    console.log(this.testValue);
  }

  changeValue(value) {
    this.testValue = value;
    set("myValue", this.testValue);
  }

  render() {
    return [
      <ion-header>
        <ion-toolbar color="primary">
          <ion-title>Home</ion-title>
        </ion-toolbar>
      </ion-header>,

      <ion-content class="ion-padding">
        <h2>Current value:</h2>

        <p>{this.testValue}</p>

        <ion-item>
          <ion-label>Change value:</ion-label>
          <ion-input onInput={(ev: any) => this.changeValue(ev.target.value)} />
        </ion-item>
      </ion-content>
    ];
  }
}

I’ve just set up a simple example here that allows the user to input any value they like into the <ion-input> area. Whenever this value is changed, it will automatically be saved to storage. If the application is refreshed, the current value will be retrieved from storage.

Summary

With the implementation above, it now doesn’t matter which platform we are running on, it will automatically use the appropriate local storage system for each platform - whether that is window.localStorage, UserDefaults, or SharedPreferences. If you are just looking for simple data storage, and you are using Capacitor already, then this is a good approach to use by default.

If you are interested in understanding more of the implementation details behind Capacitor’s Storage API, don’t forget to check out the extension video for this tutorial.

Check out my latest videos: