1.8.0
Added
Cryptographic Utilities: secureRandomBytes & getOrCreateSecret
secureRandomBytes(size: Int): ByteArray — A cross-platform cryptographically secure random byte generator, delegating to each platform's strongest CSPRNG (java.security.SecureRandom on Android/JVM, arc4random_buf on iOS, crypto.getRandomValues() on WASM). This is now also used internally by KSafe's own encryption engines for IV and key generation.
val nonce = secureRandomBytes(16)
val aesKey = secureRandomBytes(32)KSafe.getOrCreateSecret(key, size, protection, requireUnlockedDevice): ByteArray — A suspend extension that generates a cryptographically secure random secret on first call and retrieves it on subsequent calls. Stored with hardware-backed encryption (HARDWARE_ISOLATED by default). Ideal for database encryption passphrases, API signing keys, HMAC keys, or any persistent secret.
// Database passphrase — one line, hardware-backed, generated once
val passphrase = ksafe.getOrCreateSecret("main.db")
// Custom size + protection
val apiKey = ksafe.getOrCreateSecret("api_key", size = 64)Flow & StateFlow Property Delegates (#20)
Since v1.0.0, KSafe offered var counter by ksafe(0) (plain delegates) and var counter by ksafe.mutableStateOf(0) (Compose state). Version 1.8.0 adds MutableStateFlow delegates (asMutableStateFlow) as a drop-in replacement for the standard _state/state pattern, read-only flow delegates (asStateFlow / asFlow), and cross-screen sync via mutableStateOf(scope=). All new delegates derive their storage key from the property name (with an optional key override), staying consistent with the existing invoke() delegate — and the explicit-key getStateFlow() / getFlow() APIs remain fully supported.
Core Module — flow delegates
1. asMutableStateFlow (Read / Write)
Implements the full MutableStateFlow interface — all standard atomic operations work out of the box, persisting to encrypted storage instantly.
// Standard Kotlin pattern
private val _state = MutableStateFlow(MoviesListState())
val state = _state.asStateFlow()
// KSafe equivalent — same pattern, but persisted + reactive to external changes
private val _state by kSafe.asMutableStateFlow(MoviesListState(), viewModelScope)
val state = _state.asStateFlow()@Serializable
data class MoviesListState(
val loading: Boolean = false,
val movies: List<Movie> = emptyList(),
val error: String? = null
)
class MoviesViewModel(private val kSafe: KSafe, private val api: MoviesApi) : ViewModel() {
// Acts exactly like a standard MutableStateFlow, but fully persisted
private val _state by kSafe.asMutableStateFlow(MoviesListState(), viewModelScope)
val state = _state.asStateFlow()
fun loadMovies() {
// .update {} persists securely (uses compareAndSet internally)
_state.update { it.copy(loading = true) }
viewModelScope.launch {
try {
val movies = api.getMovies()
// .value = ... also persists instantly
_state.value = _state.value.copy(loading = false, movies = movies)
} catch (e: Exception) {
_state.update { it.copy(loading = false, error = e.message) }
}
}
}
}
@Composable
fun MoviesScreen(viewModel: MoviesViewModel) {
val state by viewModel.state.collectAsState()
when {
state.loading -> CircularProgressIndicator()
state.error != null -> Text("Error: ${state.error}")
else -> LazyColumn {
items(state.movies) { movie -> MovieItem(movie) }
}
}
}2. asStateFlow & asFlow (Read-Only)
If you only need to read data (or update it manually via kSafe.put()), you can use read-only flow delegates.
class SettingsViewModel(private val kSafe: KSafe) : ViewModel() {
// Hot flow tied to viewModelScope
val username: StateFlow<String> by kSafe.asStateFlow("Guest", viewModelScope)
// Cold flow
val darkMode: Flow<Boolean> by kSafe.asFlow(defaultValue = false)
// Optional: explicitly override the storage key
val theme: Flow<String> by kSafe.asFlow(defaultValue = "light", key = "app_theme")
fun onNameChanged(name: String) {
viewModelScope.launch { kSafe.put("username", name) }
}
}Compose Module — cross-screen reactivity
The existing mutableStateOf now accepts an optional scope parameter.
Without scope (existing behavior) — the state reads from cache at init and persists on write, but it's isolated. If another ViewModel or a background put() writes to the same key, this state won't update until the ViewModel is recreated.
With scope — the state continuously observes the underlying flow. Changes from any source (another screen, another ViewModel, a background coroutine) are reflected in real-time. No manual refreshes or event buses required.
If you only read/write from a single ViewModel, both behave identically. The
scopeparameter matters when multiple writers exist for the same key.
// Dashboard Screen — auto-reflects changes made from other screens
class DashboardViewModel(private val kSafe: KSafe) : ViewModel() {
var username by kSafe.mutableStateOf("Guest", scope = viewModelScope)
var notificationsEnabled by kSafe.mutableStateOf(false, scope = viewModelScope)
}
// Settings Screen — writes to the same KSafe instance
class SettingsViewModel(private val kSafe: KSafe) : ViewModel() {
var username by kSafe.mutableStateOf("Guest", scope = viewModelScope)
var notificationsEnabled by kSafe.mutableStateOf(false, scope = viewModelScope)
}
// When SettingsScreen writes, DashboardScreen auto-updates — no manual refresh
@Composable
fun DashboardScreen(viewModel: DashboardViewModel) {
Text("Welcome, ${viewModel.username}")
if (viewModel.notificationsEnabled) Text("Notifications ON")
}
@Composable
fun SettingsScreen(viewModel: SettingsViewModel) {
TextField(value = viewModel.username, onValueChange = { viewModel.username = it })
Switch(
checked = viewModel.notificationsEnabled,
onCheckedChange = { viewModel.notificationsEnabled = it }
)
}API summary
| Module | Function | Type | Returns |
|---|---|---|---|
| core | asFlow(defaultValue, key?) |
Read-only | Flow<T> delegate |
| core | asStateFlow(defaultValue, scope, key?) |
Read-only | StateFlow<T> delegate |
| core | asMutableStateFlow(defaultValue, scope, key?, mode?) |
Read/write + Reactive | MutableStateFlow<T> delegate |
| compose | mutableStateOf(..., scope?) |
Read/write + Reactive | MutableState<T> w/ flow observation |
Full Changelog: 1.7.1...1.8.0