6.2 KiB
title | description | guide_group | order | related_docs | |
---|---|---|---|---|---|
Persistent Storage | Using persistent storage to improve your app's UX. | events-and-services | 7 |
|
Developers can use the Storage
API to persist multiple types of data between
app launches, enabling apps to remember information previously entered by the
user. A common use-case of this API is to enable the app to remember
configuration data chosen in an app's configuration page, removing the
tedious need to enter the information on the phone every time the watchapp is
launched. Other use cases include to-to lists, stat trackers, game highscores
etc.
Read {% guide_link user-interfaces/app-configuration %} for more information on implementing an app configuration page.
Persistent Storage Model
Every app is allocated 4 kB of persistent storage space and can write values to
storage using a key, similar to AppMessage
dictionaries or the web
localStorage
API. To recall values, the app simply queries the API using the
associated key . Keys are specified in the uint32_t
type, and each value can
have a size up to PERSIST_DATA_MAX_LENGTH
(currently 256 bytes).
When an app is updated the values saved using the Storage
API will be
persisted, but if it is uninstalled they will be removed.
Apps that make large use of the Storage
API may experience small pauses due
to underlying housekeeping operations. Therefore it is recommended to read and
write values when an app is launching or exiting, or during a time the user is
waiting for some other action to complete.
Types of Data
Values can be stored as boolean, integer, string, or arbitrary data structure types. Before retrieving a value, the app should check that it has been previously persisted. If it has not, a default value should be chosen as appropriate.
uint32_t key = 0;
int num_items = 0;
if (persist_exists(key)) {
// Read persisted value
num_items = persist_read_int(key);
} else {
// Choose a default value
num_items = 10;
// Remember the default value until the user chooses their own value
persist_write_int(key, num_items);
}
The API provides a 'read' and 'write' function for each of these types, with builtin data types retrieved through assignment, and complex ones into a buffer provided by the app. Examples of each are shown below.
Booleans
uint32_t key = 0;
bool large_font_size = true;
// Write a boolean value
persist_write_bool(key, large_font_size);
// Read the boolean value
bool large_font_size = persist_read_bool(key);
Integers
uint32_t key = 1;
int highscore = 432;
// Write an integer
persist_write_int(key, highscore);
// Read the integer value
int highscore = persist_read_int(key);
Strings
uint32_t key = 2;
char *string = "Remember this!";
// Write the string
persist_write_string(key, string);
// Read the string
char buffer[32];
persist_read_string(key, buffer, sizeof(buffer));
Data Structures
typedef struct {
int a;
int b;
} Data;
uint32_t key = 3;
Data data = (Data) {
.a = 32,
.b = 45
};
// Write the data structure
persist_write_data(key, &data, sizeof(Data));
// Read the data structure
persist_read_data(key, &data, sizeof(Data));
Note: If a persisted data structure's field layout changes between app versions, the data read may no longer be compatible (see below).
Versioning Persisted Data
As already mentioned, automatic app updates will persist data between app versions. However, if the format of persisted data changes in a new app version (or keys change), developers should version their storage scheme and correctly handle version changes appropriately.
One way to do this is to use an extra persisted integer as the storage scheme's version number. If the scheme changes, simply update the version number and migrate existing data as required. If old data cannot be migrated it should be deleted and replaced with fresh data in the correct scheme from the user. An example is shown below:
const uint32_t storage_version_key = 786;
const int current_storage_version = 2;
// Store the current storage scheme version number
persist_write_int(storage_version_key, current_storage_version);
In this example, data stored in a key of 12
is now stored in a key of 13
due
to a new key being inserted higher up the list of key values.
// The scheme has changed, increment the version number
const int current_storage_version = 3;
static void migrate_storage_data() {
// Check the last storage scheme version the app used
int last_storage_version = persist_read_int(storage_version_key);
if (last_storage_version == current_storage_version) {
// No migration necessary
return;
}
// Migrate data
switch(last_storage_version) {
case 0:
// ...
break;
case 1:
// ...
break;
case 2: {
uint32_t old_highscore_key = 12;
uint32_t new_highscore_key = 13;
// Migrate to scheme version 3
int highscore = persist_read_int(old_highscore_key);
persist_write_int(new_highscore_key, highscore);
// Delete old data
persist_delete(old_highscore_key);
break;
}
// Migration is complete, store the current storage scheme version number
persist_write_int(storage_version_key, current_storage_version);
}
Alternative Method
In addition to the Storage
API, data can also be persisted using the
localStorage
API in PebbleKit JS, and communicated with the watch over
AppMessage
when the app is lanched. However, this method uses more power and
fails if the watch is not connected to the phone.