One Logo Pool Ball
wwwwwwwwwwwwwwwwwww

Storage

The app's synchronous key-value store, backed by a crash-safe log on iOS and Android

One.Storage is the app’s key-value store: string keys, string values, and every call synchronous, with the same method names as the web’s localStorage. Use it for settings, small caches, and app state you want back on the next launch.

On iOS and Android it is one C++ engine that keeps every entry in memory and records each write in a memory-mapped log, so reads cost a lookup and writes survive a crash. On web it is localStorage. For secrets use Secure Store; for structured or large data, or for several separate stores, use Database.

Usage

import { useState } from 'react'
import { Switch } from 'react-native'
import { One } from 'one'
function DarkModeSetting() {
const [dark, setDark] = useState(() => One.Storage.getItem('theme') === 'dark')
return (
<Switch
value={dark}
onValueChange={(next) => {
setDark(next)
One.Storage.setItem('theme', next ? 'dark' : 'light')
}}
/>
)
}

Because reads are synchronous, the saved value is there on the first render, with no loading state and no flash of the default.

How it works

One engine for both platforms. The store is a Nitro module written in C++, called straight through JSI. iOS and Android share the same code; only the folder differs: Application Support on iOS and the app’s files folder on Android, both private to the app.

Reads. The first call replays the log once and builds a map of every key to where its value sits in the mapped file. After that a read is a lookup and a copy out of memory; it never waits on the disk.

Writes. A write appends one record to the log through the memory map, with no system call. The mapped pages belong to the operating system, so once setItem returns, the value survives the app crashing. Each record’s type byte is written after its body, so a crash mid-write leaves either the whole record or nothing, and the next launch replays up to the last whole one.

Compaction. When the log fills, it is rewritten as a snapshot of the live entries if at least half of it is dead, or doubled otherwise. The snapshot is written to a temporary file, synced, and renamed over the log, so a crash during compaction leaves one complete log or the other.

Web. Each key is stored in localStorage with a One.Storage. prefix, so getAllKeys lists only this store’s entries.

API reference

MethodReturns
getItem(key)The value, or null
setItem(key, value)Nothing
removeItem(key)Nothing
getAllKeys()Every key in the store

Platform differences

iOSAndroidWeb
EngineC++ logC++ logLocal storage
FolderApplication SupportApp filesThe browser’s
Crash safeOnce the call returnsOnce the call returnsThe browser’s

Limits and gotchas

Examples

Save an object

import { One } from 'one'
type Filters = { sort: 'new' | 'top'; tags: string[] }
const defaults: Filters = { sort: 'new', tags: [] }
function loadFilters(): Filters {
const saved = One.Storage.getItem('feed.filters')
return saved ? { ...defaults, ...JSON.parse(saved) } : defaults
}
function saveFilters(filters: Filters) {
One.Storage.setItem('feed.filters', JSON.stringify(filters))
}

Spreading over defaults keeps older saved data working when you add a field. The feed. prefix keeps this feature’s keys apart from others.

Show onboarding once

import { useState } from 'react'
import { Button, Text, View } from 'react-native'
import { One } from 'one'
function Welcome({ children }: { children: React.ReactNode }) {
const [seen, setSeen] = useState(() => One.Storage.getItem('onboarding.done') !== null)
if (seen) return <>{children}</>
return (
<View>
<Text>Welcome</Text>
<Button
title="Get started"
onPress={() => {
One.Storage.setItem('onboarding.done', '1')
setSeen(true)
}}
/>
</View>
)
}

The check runs during the first render, so people who finished onboarding never see it flash before the app.

Edit this page on GitHub.