mirror of
https://github.com/google/pebble.git
synced 2025-03-21 19:31:20 +00:00
241 lines
6.2 KiB
Markdown
241 lines
6.2 KiB
Markdown
|
---
|
||
|
# Copyright 2025 Google LLC
|
||
|
#
|
||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
# you may not use this file except in compliance with the License.
|
||
|
# You may obtain a copy of the License at
|
||
|
#
|
||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||
|
#
|
||
|
# Unless required by applicable law or agreed to in writing, software
|
||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
# See the License for the specific language governing permissions and
|
||
|
# limitations under the License.
|
||
|
|
||
|
title: Persistent Storage
|
||
|
description: |
|
||
|
Using persistent storage to improve your app's UX.
|
||
|
guide_group: events-and-services
|
||
|
order: 7
|
||
|
related_docs:
|
||
|
- Storage
|
||
|
---
|
||
|
|
||
|
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.
|
||
|
|
||
|
```c
|
||
|
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
|
||
|
|
||
|
```c
|
||
|
uint32_t key = 0;
|
||
|
bool large_font_size = true;
|
||
|
```
|
||
|
|
||
|
```c
|
||
|
// Write a boolean value
|
||
|
persist_write_bool(key, large_font_size);
|
||
|
```
|
||
|
|
||
|
```c
|
||
|
// Read the boolean value
|
||
|
bool large_font_size = persist_read_bool(key);
|
||
|
```
|
||
|
|
||
|
|
||
|
### Integers
|
||
|
|
||
|
```c
|
||
|
uint32_t key = 1;
|
||
|
int highscore = 432;
|
||
|
```
|
||
|
|
||
|
```c
|
||
|
// Write an integer
|
||
|
persist_write_int(key, highscore);
|
||
|
```
|
||
|
|
||
|
```c
|
||
|
// Read the integer value
|
||
|
int highscore = persist_read_int(key);
|
||
|
```
|
||
|
|
||
|
|
||
|
### Strings
|
||
|
|
||
|
```c
|
||
|
uint32_t key = 2;
|
||
|
char *string = "Remember this!";
|
||
|
```
|
||
|
|
||
|
```c
|
||
|
// Write the string
|
||
|
persist_write_string(key, string);
|
||
|
```
|
||
|
|
||
|
```c
|
||
|
// Read the string
|
||
|
char buffer[32];
|
||
|
persist_read_string(key, buffer, sizeof(buffer));
|
||
|
```
|
||
|
|
||
|
|
||
|
### Data Structures
|
||
|
|
||
|
```c
|
||
|
typedef struct {
|
||
|
int a;
|
||
|
int b;
|
||
|
} Data;
|
||
|
|
||
|
uint32_t key = 3;
|
||
|
Data data = (Data) {
|
||
|
.a = 32,
|
||
|
.b = 45
|
||
|
};
|
||
|
```
|
||
|
|
||
|
```c
|
||
|
// Write the data structure
|
||
|
persist_write_data(key, &data, sizeof(Data));
|
||
|
```
|
||
|
|
||
|
```c
|
||
|
// 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:
|
||
|
|
||
|
```c
|
||
|
const uint32_t storage_version_key = 786;
|
||
|
const int current_storage_version = 2;
|
||
|
```
|
||
|
|
||
|
```c
|
||
|
// 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.
|
||
|
|
||
|
```c
|
||
|
// The scheme has changed, increment the version number
|
||
|
const int current_storage_version = 3;
|
||
|
```
|
||
|
|
||
|
```c
|
||
|
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.
|
||
|
|