DataStores (Local Storage)¶
The Starter Template provides a unified DataStore setup for local storage in Kotlin Multiplatform projects.
You can use it in both Compose and non-Compose code for storing simple preferences like String, Int, Boolean, Long, or Set<String>.
Compose-Friendly Usage¶
Immutable State¶
Use rememberDataStoreValue to read values as immutable state:
val themeMode by rememberStringDataStore("theme_mode", "LIGHT")
val isDynamic by rememberBooleanDataStore("dynamic_colors", false)
Other helpers:
val counter by rememberIntDataStore("counter", 0)
val tags by rememberStringSetDataStore("tags", emptySet())
These automatically collect DataStore values as a State<T> that updates on changes.
Mutable State¶
Use rememberMutableDataStoreState to read and write values:
var themeMode by rememberMutableStringDataStore("theme_mode", "LIGHT")
var isDynamic by rememberMutableBooleanDataStore("dynamic_colors", false)
// Update value
themeMode = "DARK"
isDynamic = true
// Remove value by setting it to null
themeMode = null
Other helpers:
var counter by rememberMutableIntDataStore("counter", 0)
var lastSync by rememberMutableLongDataStore("lastSync", 0L)
Note: In Compose, setting a mutable DataStore state to
nullwill remove the key from DataStore.
Non-Compose Usage¶
Recommended: use DataStore delegates from AppDataStore inside a ViewModel (or any class). Skip raw Preferences.Key + edit/map boilerplate.
DataStore Delegates¶
class AuthViewModel(
appDataStore: AppDataStore,
) : ViewModel() {
private val accessToken = appDataStore.stringDataStore("access_token")
private val loginCount = appDataStore.intDataStore("login_count", default = 0)
private val isLoggedIn = appDataStore.booleanDataStore("is_logged_in", default = false)
fun login(token: String) {
viewModelScope.launch {
accessToken.set(token)
isLoggedIn.set(true)
loginCount.set((loginCount.get() ?: 0) + 1)
}
}
fun logout() {
viewModelScope.launch {
accessToken.clear()
isLoggedIn.set(false)
}
}
// Observe
val tokenFlow = accessToken.flow
}
Primitive factories on AppDataStore:
| Factory | Type |
|---|---|
stringDataStore |
String |
intDataStore |
Int |
longDataStore |
Long |
booleanDataStore |
Boolean |
floatDataStore |
Float |
doubleDataStore |
Double |
stringSetDataStore |
Set<String> |
byteArrayDataStore |
ByteArray |
API on each delegate: flow, get(), set(value), clear(). Pass null to set → key removed.
Serializable objects¶
Store any @Serializable type as JSON:
@Serializable
data class Settings(val darkMode: Boolean = false)
class SettingsViewModel(
appDataStore: AppDataStore,
) : ViewModel() {
private val settings = appDataStore.serializableDataStore(
name = "settings",
default = Settings(),
)
val settingsFlow = settings.flow
fun setDarkMode(enabled: Boolean) {
viewModelScope.launch {
settings.set(Settings(darkMode = enabled))
}
}
}
Raw DataStore (not recommended)¶
Only if you need custom key logic beyond delegates:
class AuthViewModel(
appDataStore: AppDataStore,
) : ViewModel() {
companion object {
private val ACCESS_TOKEN_KEY = stringPreferencesKey("access_token")
}
private val dataStore = appDataStore.dataStore
val accessTokenFlow = dataStore.data.map { prefs ->
prefs[ACCESS_TOKEN_KEY]
}
fun setAccessToken(token: String?) {
viewModelScope.launch {
dataStore.edit { prefs ->
if (token == null) prefs.remove(ACCESS_TOKEN_KEY)
else prefs[ACCESS_TOKEN_KEY] = token
}
}
}
}
Prefer delegates
Non-Compose code → stringDataStore / intDataStore / serializableDataStore etc. Less boilerplate, same behavior.
Note
Inject AppDataStore into your ViewModel via Koin (it is a singleton).
Support My Project ☕️¶
If you find this project useful, consider supporting it by buying me a coffee. Your support will help me to continue working on this project and add more features.