Adding DataStore to a Kotlin Multiplatform Project
Step-by-Step Tutorial: We will take a Compose Multiplatform baseline project and add DataStore for use on Android, iOS, and Desktop.
Overview
In this tutorial, I am speed running through adding a DataStore
to a Kotlin Multiplatform Compose project to persistently track a counter. This guide is based on Philipp Lackner's YouTube video and his GitHub repository.
Prerequisites
Basic understanding of Kotlin Multiplatform (KMP)
Basic understanding of Compose Multiplatform (CMP)
1. Create a New KMP Project
Visit kmp.jetbrains.com and create a new project - or you can download the initial commit at https://github.com/dyor/DataStoreSample/tree/ee07a3440ed1a71856d1e15e5614222d857c044b
2. Add DataStore Dependencies
Open your
libs.versions.toml
file and add the following:[versions] datastore = "1.1.1" [libraries] datastore ={module = "androidx.datastore:datastore", version.ref = "datastore"} datastore-preferences = {module = "androidx.datastore:datastore-preferences", version.ref = "datastore"}
In the
build.gradle.kts
for thecommonMain
source set, add the following dependencies using api (instead of implementation) so that the datastore dependencies will be available to all of the source sets (not just commonMain):commonMain.dependencies { ... api(libs.datastore) api(libs.datastore.preferences) }
- Ensure you Sync Now
3. Create a Shared DataStore Factory
In the
commonMain
source set, create a file namedcreateDataStore.kt
.Define a function to initialize the
DataStore
:import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.PreferenceDataStoreFactory import okio.Path.Companion.toPath internal const val DATASTORE_FILE_NAME = "prefs.preferences_pb" fun createDataStore(producePath: () -> String): DataStore<Preferences> { return PreferenceDataStoreFactory.createWithPath { producePath().toPath() } }
4. Platform-Specific Implementations
Android
In
androidMain
, create a filecreateDataStore.kt
(and then rename it tocreateDataStore.android.kt
):import android.content.Context import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences fun createDataStore(context: Context): DataStore<Preferences> { return createDataStore { context.filesDir.resolve(DATASTORE_FILE_NAME).absolutePath } }
This is now at 6:38 in Phillip’s video and here is the commit in GitHub.
iOS and Desktop
I am speed-running Android for now - I will add iOS and web later.
5. Get prefs into Android App Entry Point
- In commonMain > App.kt, receive a
prefs
argument in the App function:
@Composable
@Preview
fun App(
prefs: DataStore<Preferences>
)
- In androidMain > MainActivity.kt, provide a prefs argument in the App function (including a remember so that it survives across recompositions):
setContent {
App(
prefs = remember {
createDataStore(applicationContext)
}
)
}
5. Implement the Counter Feature
In commonMain > App.kt, add this within the App function:
{
val counter by prefs
.data
.map {
val counterKey = intPreferencesKey("counter")
it[counterKey] ?: 0
}
.collectAsState(initial = 0)
val scope = rememberCoroutineScope()
MaterialTheme {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center) {
Text(
text = counter.toString(),
textAlign = TextAlign.Center,
fontSize = 50.sp,
)
Button(onClick = {
scope.launch {
prefs.edit { dataStore ->
val counterKey = intPreferencesKey("counter")
dataStore[counterKey] = counter + 1
}
}
}) {
Text("Increment!")
}
}
}
}
You should now have a working solution. If you want to check your work, here is a link to the relevant commit.
Conclusion
You've successfully integrated DataStore
into a KMP project! This setup ensures a consistent and robust way to persist data across platforms. For more advanced use cases, check out the official DataStore documentation.