expo-native-storage
Version:
Lightning-fast native storage for Expo.
232 lines (170 loc) • 7.32 kB
Markdown
# expo-native-storage
[](https://badge.fury.io/js/expo-native-storage)
[](https://npmjs.org/package/expo-native-storage)
Lightning-fast native storage for Expo using UserDefaults and SharedPreferences.
## Why?
- **Fast**: Direct native storage, no bridge delays
- **Tiny**: 19x smaller bundle size compared to AsyncStorage
- **Scales**: Gets faster with more operations (up to 32x on Android)
- **Web support**: Falls back to localStorage
- **Native**: Uses UserDefaults (iOS) & SharedPreferences (Android)
## Installation
```bash
# bun
bunx expo install expo-native-storage
# npm
npx expo install expo-native-storage
```
> [!IMPORTANT]
> After installation, you must rebuild your app to link the native module. This module requires native code and will **not** work with Expo Go. You need a development build or production build.
```bash
# For development builds
npx expo prebuild --clean
npx expo run:ios
# or
npx expo run:android
```
This module requires native code and will **not** work with Expo Go. You need a development build or production build.
## Usage
### Async API (Promise-based)
```typescript
import Storage from 'expo-native-storage';
// Store strings
await Storage.setItem('username', 'john_doe');
const username = await Storage.getItem('username'); // 'john_doe'
// Store objects
await Storage.setObject('user', {
name: 'John',
age: 30,
premium: true
});
const user = await Storage.getObject('user');
// Remove items
await Storage.removeItem('username');
// Clear all
await Storage.clear();
```
### Sync API (Immediate, no Promises)
```typescript
import Storage from 'expo-native-storage';
// Store strings synchronously
Storage.setItemSync('username', 'john_doe');
const username = Storage.getItemSync('username'); // 'john_doe'
// Store objects synchronously
Storage.setObjectSync('user', {
name: 'John',
age: 30,
premium: true
});
const user = Storage.getObjectSync('user');
// Remove items synchronously
Storage.removeItemSync('username');
// Clear all synchronously
Storage.clearSync();
```
**When to use sync vs async:**
- **Sync methods**: App initialization, reading config on startup, settings screens
- **Async methods**: When you need to coordinate with other async operations, or prefer Promise-based code
## Example: Theme Storage
```typescript
// Save theme preference
await Storage.setItem('theme', 'dark');
// Load on app start
const theme = await Storage.getItem('theme') || 'light';
```
## API
### Async Methods
| Method | Description | Return |
|--------|-------------|---------|
| `setItem(key, value)` | Store a string value | `Promise<void>` |
| `getItem(key)` | Get a string value | `Promise<string \| null>` |
| `setObject(key, object)` | Store an object as JSON | `Promise<void>` |
| `getObject<T>(key)` | Get an object from JSON | `Promise<T \| null>` |
| `removeItem(key)` | Remove an item | `Promise<void>` |
| `clear()` | Remove all items | `Promise<void>` |
| `multiGet(keys)` | Get multiple items at once | `Promise<Record<string, string \| null>>` |
| `multiSet(items)` | Set multiple items at once | `Promise<void>` |
### Sync Methods
| Method | Description | Return |
|--------|-------------|---------|
| `setItemSync(key, value)` | Store a string value | `void` |
| `getItemSync(key)` | Get a string value | `string \| null` |
| `setObjectSync(key, object)` | Store an object as JSON | `void` |
| `getObjectSync<T>(key)` | Get an object from JSON | `T \| null` |
| `removeItemSync(key)` | Remove an item | `void` |
| `clearSync()` | Remove all items | `void` |
## Performance Results
### Async Methods - Real Device Testing:
| Platform | Operations | expo-native-storage | AsyncStorage | Improvement |
|----------|------------|-------------------|--------------|-------------|
| **Android Phone** | 100 ops | 11ms | 165ms | **15x faster** |
| | 200 ops | ~25ms | ~290ms | **11x faster** |
| | 500 ops | ~50ms | ~650ms | **13x faster** |
| | 1000 ops | ~95ms | ~1216ms | **13x faster** |
| **Android Emulator** | 100 ops | 12ms | 219ms | **18x faster** |
| **iOS Phone** | 100 ops | 72ms | 72ms | Same speed |
| **Bundle Size** | - | 19.6KB | 381KB | **19x smaller** |
### Sync Methods - Real Device Testing:
| Platform | Operations | Sync Methods | Async Methods | Improvement |
|----------|------------|--------------|---------------|-------------|
| **Android Phone** | 1000 ops | 98ms | 472ms | **4.8x faster** |
| **iOS Phone** | 1000 ops | 1098ms | 1499ms | **1.4x faster** |
**Note on iOS Performance:** iOS sync methods are limited by UserDefaults disk I/O (~1ms per write). For bulk operations (1000+ writes), consider using specialized libraries like [react-native-mmkv](https://github.com/mrousavy/react-native-mmkv). Sync methods are perfect for typical use cases like app settings and user preferences.
*Tested on iPhone 17 Pro and Nothing Phone 3a with Android 15.*
*Emulator results from MacBook Pro M4 with 16GB RAM.*
## Performance at Scale
The performance advantage of expo-native-storage **increases** with usage:
**Why?** SharedPreferences uses in-memory caching after first access, while AsyncStorage hits the SQLite database for every operation. The more operations you perform, the bigger the performance gap becomes.
**Perfect for:**
- Settings screens with many preferences
- Offline data caching with frequent reads
- State persistence with frequent updates
- User session data with multiple keys
## Migration from AsyncStorage
```typescript
// Before (AsyncStorage)
import AsyncStorage from '@react-native-async-storage/async-storage';
await AsyncStorage.setItem('key', 'value');
// After (expo-native-storage)
import Storage from 'expo-native-storage';
await Storage.setItem('key', 'value');
```
Drop-in replacement with zero breaking changes.
## Platform Notes
- **iOS**: Uses `UserDefaults` (synchronous, limited to ~1MB per key)
- **Android**: Uses `SharedPreferences` (synchronous, good for small data)
- **Web**: Uses `localStorage` (synchronous, ~5-10MB limit)
## Requirements
- Expo SDK 50+
- Development builds (not available in Expo Go)
## Troubleshooting
### Error: Cannot find native module 'ExpoNativeStorage'
This error means the native module isn't linked. To fix:
1. **Rebuild your app** after installing:
```bash
bunx expo prebuild --clean
bunx expo run:ios # or run:android
```
2. **Clear caches** if the issue persists:
```bash
# clear Metro bundler cache
bunx expo start -c
# clear all caches
rm -rf node_modules
bun install # or npm install
bunx expo prebuild --clean
```
3. **Verify installation**:
- Check that `expo-native-storage` is in your `package.json`
- Make sure you're using a development build, not Expo Go
- For iOS: Check that `ExpoNativeStorage` appears in your Podfile.lock
- For Android: Check that the module is in your build.gradle dependencies
### Still having issues?
Open an issue on [GitHub](https://github.com/thereallo1026/expo-native-storage/issues) with:
- Your Expo SDK version
- React Native version
- Platform (iOS/Android)
- Full error message
- Whether you're using Expo Go or a development build
## License
MIT