Skip to main content

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.

StorageModuleBest For
MemoryStoragetoggly-android-coreTesting, no persistence needed
SharedPreferencesStoragetoggly-android-coreSimple apps, fast access
RoomStoragetoggly-roomComplex apps, relational data
DataStoreStoragetoggly-datastoreModern 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​