Adding DataStore to a Kotlin Multiplatform Project

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.

·

3 min read

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

  1. 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"}
    
  2. In the build.gradle.kts for the commonMain 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

  1. In the commonMain source set, create a file named createDataStore.kt.

  2. 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

  1. In androidMain, create a file createDataStore.kt (and then rename it to createDataStore.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

  1. In commonMain > App.kt, receive a prefs argument in the App function:
@Composable
@Preview
fun App(
    prefs: DataStore<Preferences>
)
  1. 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.