Storage Options
Choose the right storage adapter for your Android application's feature flag persistence.
Overview
The Toggly Android SDK supports multiple storage options for persisting feature flags. Each option has different trade-offs for performance, reliability, and complexity.
| Storage | Module | Best For |
|---|---|---|
| MemoryStorage | toggly-android-core | Testing, no persistence needed |
| SharedPreferencesStorage | toggly-android-core | Simple apps, fast access |
| RoomStorage | toggly-room | Complex apps, relational data |
| DataStoreStorage | toggly-datastore | Modern apps, type safety |
MemoryStorage
In-memory storage with no persistence. Data is lost when the app is closed.
import io.toggly.core.storage.MemoryStorage
Toggly.configure(
config = config,
storage = MemoryStorage()
)
Best for:
- Unit testing
- Local-only mode with default flags
- Apps that don't need offline support
SharedPreferencesStorage
Uses Android's SharedPreferences for simple key-value storage.
import io.toggly.core.storage.SharedPreferencesStorage
Toggly.configure(
config = config,
storage = SharedPreferencesStorage(
context = applicationContext,
name = "toggly_prefs" // Optional, defaults to "toggly_prefs"
)
)
Pros:
- Built into the core module (no additional dependencies)
- Fast read/write operations
- Simple API
Cons:
- Synchronous file I/O on the main thread for writes
- No encryption by default
- Limited to primitive types and strings
Best for:
- Most applications
- Quick setup
- Apps with few feature flags
RoomStorage
Uses Android Room for SQLite-based persistence.
Installation
dependencies {
implementation("io.toggly:toggly-android-core:1.0.0")
implementation("io.toggly:toggly-room:1.0.0")
}
Usage
import io.toggly.room.createRoomStorage
Toggly.configure(
config = config,
storage = createRoomStorage(
context = applicationContext,
databaseName = "toggly.db" // Optional, defaults to "toggly.db"
)
)
Alternative: Direct Database Access
import io.toggly.room.TogglyDatabase
import io.toggly.room.RoomStorage
// Get or create the database
val database = TogglyDatabase.getInstance(context, "toggly.db")
val storage = RoomStorage(database)
Toggly.configure(config = config, storage = storage)
Pros:
- Full async/coroutine support
- SQLite reliability
- Efficient for large data sets
- Migration support
Cons:
- Larger bundle size
- More complex setup
- Requires Room dependencies
Best for:
- Apps already using Room
- Large number of feature flags
- Apps needing migration support
Database Schema
The Room storage uses a simple table:
@Entity(tableName = "toggly_storage")
data class TogglyStorageEntity(
@PrimaryKey val key: String,
val value: String,
val updatedAt: Long = System.currentTimeMillis()
)
Additional Methods
RoomStorage provides extra methods beyond the base interface:
val roomStorage = createRoomStorage(context) as RoomStorage
// Get all keys
val keys: List<String> = roomStorage.keys()
// Get count of stored items
val count: Int = roomStorage.size()
DataStoreStorage
Uses AndroidX DataStore for modern, coroutine-first persistence.
Installation
dependencies {
implementation("io.toggly:toggly-android-core:1.0.0")
implementation("io.toggly:toggly-datastore:1.0.0")
}
Usage
import io.toggly.datastore.createDataStoreStorage
Toggly.configure(
config = config,
storage = createDataStoreStorage(applicationContext)
)
Alternative: Context Extension
import io.toggly.datastore.togglyStorage
Toggly.configure(
config = config,
storage = context.togglyStorage
)
Pros:
- Fully async/coroutine-based
- Type-safe
- Consistent (never partial updates)
- Google's recommended replacement for SharedPreferences
Cons:
- Requires DataStore dependencies
- Slightly more complex API
- Cannot observe individual keys (only whole file)
Best for:
- Modern Kotlin-first apps
- Apps already using DataStore
- Apps requiring consistency guarantees
Additional Methods
DataStoreStorage provides reactive observation and batch operations:
val dataStoreStorage = createDataStoreStorage(context)
// Observe a key reactively
dataStoreStorage.observe("feature-flags").collect { value ->
// React to changes
}
// Check if key exists
val exists: Boolean = dataStoreStorage.contains("my-key")
// Get multiple values at once
val values: Map<String, String?> = dataStoreStorage.getMultiple(
listOf("key1", "key2", "key3")
)
// Set multiple values at once
dataStoreStorage.setMultiple(mapOf(
"key1" to "value1",
"key2" to "value2"
))
// Get all keys
val keys: List<String> = dataStoreStorage.keys()
// Get count
val count: Int = dataStoreStorage.size()
Custom Storage
Implement the TogglyStorage interface for custom storage solutions:
import io.toggly.core.models.TogglyStorage
class MyCustomStorage : TogglyStorage {
override suspend fun get(key: String): String? {
// Return the value for the key, or null if not found
}
override suspend fun set(key: String, value: String) {
// Store the key-value pair
}
override suspend fun delete(key: String) {
// Remove the key
}
override suspend fun clear() {
// Remove all keys
}
}
// Use your custom storage
Toggly.configure(
config = config,
storage = MyCustomStorage()
)
Example: Encrypted Storage
class EncryptedStorage(
context: Context
) : TogglyStorage {
private val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
private val sharedPreferences = EncryptedSharedPreferences.create(
context,
"toggly_encrypted",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
override suspend fun get(key: String): String? =
sharedPreferences.getString(key, null)
override suspend fun set(key: String, value: String) {
sharedPreferences.edit().putString(key, value).apply()
}
override suspend fun delete(key: String) {
sharedPreferences.edit().remove(key).apply()
}
override suspend fun clear() {
sharedPreferences.edit().clear().apply()
}
}
Migration Between Storage Types
When changing storage types, you may need to migrate existing data:
suspend fun migrateStorage(
from: TogglyStorage,
to: TogglyStorage
) {
// Get all keys from old storage (if supported)
val keys = when (from) {
is MemoryStorage -> from.keys()
is RoomStorage -> from.keys()
is DataStoreStorage -> from.keys()
else -> emptyList()
}
// Copy all values
keys.forEach { key ->
from.get(key)?.let { value ->
to.set(key, value)
}
}
// Clear old storage
from.clear()
}
Best Practices
1. Choose Based on App Requirements
// Simple app with few flags
val storage = SharedPreferencesStorage(context)
// Complex app already using Room
val storage = createRoomStorage(context)
// Modern Kotlin-first app
val storage = createDataStoreStorage(context)
2. Use Application Context
// Always use application context to avoid memory leaks
Toggly.configure(
config = config,
storage = SharedPreferencesStorage(applicationContext)
)
3. Handle Storage Errors
try {
Toggly.configure(config = config, storage = storage)
Toggly.shared.init()
} catch (e: Exception) {
// Fall back to memory storage
Toggly.configure(config = config, storage = MemoryStorage())
Toggly.shared.init()
}
Next Steps
- Learn about Configuration options
- Explore Advanced Usage patterns
- Check the API Reference