<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Rendy Adidarma</title>
    <description>The latest articles on DEV Community by Rendy Adidarma (@rendyadidarma).</description>
    <link>https://dev.to/rendyadidarma</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F611792%2F17007a5f-bcd2-4f0c-87e8-8e173be9cc40.jpeg</url>
      <title>DEV Community: Rendy Adidarma</title>
      <link>https://dev.to/rendyadidarma</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rendyadidarma"/>
    <language>en</language>
    <item>
      <title>How to Implement Preferences DataStore for Compose Multiplatform Mobile (Android and iOS)</title>
      <dc:creator>Rendy Adidarma</dc:creator>
      <pubDate>Sun, 09 Mar 2025 14:48:27 +0000</pubDate>
      <link>https://dev.to/rendyadidarma/how-to-implement-preferences-datastore-for-compose-multiplatform-mobile-android-and-ios-f8</link>
      <guid>https://dev.to/rendyadidarma/how-to-implement-preferences-datastore-for-compose-multiplatform-mobile-android-and-ios-f8</guid>
      <description>&lt;p&gt;&lt;strong&gt;&lt;em&gt;What is Preferences Data Store?&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
Preferences DataStore is a modern way to store small amounts of &lt;em&gt;key-value&lt;/em&gt; data in Android, replacing SharedPreferences. It is more efficient because it uses Kotlin Flow for async data handling and ensures data consistency.&lt;/p&gt;

&lt;p&gt;By the end of this guide, you’ll have a working DataStore setup that allows you to store and retrieve key-value preferences across both platforms. (in Compose Multiplatform :D)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F12480%2F0%2AyunJoBJe2JIgaWLl" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F12480%2F0%2AyunJoBJe2JIgaWLl" alt="Photo by [Claudio Schwarz](https://unsplash.com/@purzlbaum?utm_source=medium&amp;amp;utm_medium=referral) on [Unsplash](https://unsplash.com?utm_source=medium&amp;amp;utm_medium=referral)" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Setting Up Data Store in Compose Multiplatform Project.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In this article, we will use &lt;strong&gt;Koin&lt;/strong&gt; to manage dependency injection in our project. Koin is a lightweight and easy-to-use DI framework that helps us organize and inject dependencies efficiently.&lt;br&gt;
 We will set up &lt;strong&gt;Preferences DataStore&lt;/strong&gt; for Android and iOS separately, and use Koin to provide instances of these storage solutions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Add Dependencies&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In your *libs.versions.toml *file add this versions and libraries:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[versions]

datastore = "1.1.3"
koin = "3.5.6"
koinCompose = "1.1.5"
koinComposeViewModel = "1.2.0-Beta4"

[libraries]

datastore = { module = "androidx.datastore:datastore", version.ref = "datastore" }
datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastore" }
koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koinCompose" }
koin-compose-viewmodel = { module = "io.insert-koin:koin-compose-viewmodel", version.ref = "koinComposeViewModel" }
koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In your &lt;strong&gt;shared module/composeApp (build.gradle.kts)&lt;/strong&gt;, add:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sourceSets {
        androidMain.dependencies {
            implementation(libs.koin.android)
        }
        commonMain.dependencies {
            implementation(libs.koin.core)
            implementation(libs.koin.compose)
            implementation(libs.koin.compose.viewmodel)

            implementation(libs.datastore)
            implementation(libs.datastore.preferences)
        }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Create a DataStore instance in **commonMain **package.&lt;/p&gt;

&lt;p&gt;Save this as &lt;strong&gt;DataStoreInstance.kt&lt;/strong&gt; in commonMain:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
import androidx.datastore.preferences.core.Preferences
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.internal.SynchronizedObject
import kotlinx.coroutines.internal.synchronized
import okio.Path.Companion.toPath

@OptIn(InternalCoroutinesApi::class)
private val lock = SynchronizedObject() // Used for thread safety
private lateinit var dataStore: DataStore&amp;lt;Preferences&amp;gt; // Late-initialized variable

@OptIn(InternalCoroutinesApi::class)
fun createDataStore(producePath: () -&amp;gt; String): DataStore&amp;lt;Preferences&amp;gt; {
    return synchronized(lock) {
        if (::dataStore.isInitialized) {
            dataStore
        } else {
            PreferenceDataStoreFactory.createWithPath(produceFile = { producePath().toPath() })
                .also { dataStore = it }
        }
    }
}

internal const val DATA_STORE_FILE_NAME = "storage.preferences_pb"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Call this function in Android with path argument &lt;strong&gt;(DataStoreInstance.android.kt)&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences

fun createDataStore(context: Context): DataStore&amp;lt;Preferences&amp;gt; {
    return createDataStore {
        context.filesDir.resolve(DATA_STORE_FILE_NAME).absolutePath
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Also, we need to call this function in iOS (&lt;strong&gt;DataStoreInstance.ios.kt)&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import kotlinx.cinterop.ExperimentalForeignApi
import platform.Foundation.NSDocumentDirectory
import platform.Foundation.NSFileManager
import platform.Foundation.NSUserDomainMask

@OptIn(ExperimentalForeignApi::class)
fun createDataStore(): DataStore&amp;lt;Preferences&amp;gt; {
    return createDataStore {
        val directory = NSFileManager.defaultManager.URLForDirectory(
            directory = NSDocumentDirectory,
            inDomain = NSUserDomainMask,
            appropriateForURL = null,
            create = false,
            error = null,
        )

        requireNotNull(directory).path + "/$DATA_STORE_FILE_NAME"
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;DataStore config setup is complete, now we need to setup &lt;strong&gt;Koin for Dependency Injection&lt;/strong&gt;, the purposes is to inject or provide DataStore in our &lt;em&gt;repository *or *viewmodel.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;commonMain&lt;/strong&gt;, create a DataStoreModule with expected variable inside it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;commonMain (DataStoreModule.kt)&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import org.koin.core.module.Module

expect val dataStoreModule: Module
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Add the actual declaration in &lt;strong&gt;androidMain&lt;/strong&gt; and &lt;strong&gt;iosMain :&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9w8w4igp4v484rvmy22l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9w8w4igp4v484rvmy22l.png" alt="Initialize Actual Declaration" width="800" height="635"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;androidMain (DataStoreModule.android.kt)&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import com.rainday.datastorecmp.createDataStore
import org.koin.android.ext.koin.androidContext
import org.koin.core.module.Module
import org.koin.dsl.module

actual val dataStoreModule: Module
 get() = module { single { createDataStore(androidContext()) } }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;iosMain (DataStoreModule.ios.kt)&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;actual val dataStoreModule: Module
    get() = module { single { createDataStore() } }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Notice in our &lt;strong&gt;Android implementation&lt;/strong&gt;, we require a &lt;strong&gt;Context&lt;/strong&gt; to create the &lt;strong&gt;Preferences DataStore&lt;/strong&gt;, while in &lt;strong&gt;iOS&lt;/strong&gt;, there is &lt;strong&gt;no context dependency&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To handle this difference, we can modify our &lt;strong&gt;Koin initialization function (initKoin)&lt;/strong&gt; to accept a &lt;strong&gt;configuration function (config)&lt;/strong&gt;. This allows us to set up the &lt;strong&gt;Android-specific context&lt;/strong&gt; without affecting iOS platform.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// commonMain
fun initKoin(
    config: (KoinApplication.() -&amp;gt; Unit)? = null
) {
    startKoin {
        config?.invoke(this)

        modules(dataStoreModule)
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Next, we need to initialize the Koin in &lt;strong&gt;Android&lt;/strong&gt; and &lt;strong&gt;iOS&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;androidMain &lt;strong&gt;(Application Class)&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class YourApplicationClass: Application() {
    override fun onCreate() {
        super.onCreate()
        initKoin(
            config = {
                androidContext(this@BaseApplication)
            }
        )
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;iosMain &lt;strong&gt;(MainViewController.kt)&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fun MainViewController() = ComposeUIViewController(
    configure = {
        initKoin()
    }
) { App() }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;That’s it! Our Preferences DataStore is now ready to be used in a Repository or ViewModel.&lt;/p&gt;

&lt;p&gt;Here’s an example of how you can use it:&lt;/p&gt;

&lt;p&gt;ViewModel in **commonMain **package&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class AppViewModel(
    private val dataStore: DataStore&amp;lt;Preferences&amp;gt;
): ViewModel() {

    private val key = stringPreferencesKey("name")

    private var _name = MutableStateFlow("")
    val name = _name.asStateFlow()

    init {
        viewModelScope.launch {
            dataStore.data.collect { storedData -&amp;gt;
                _name.update {
                    storedData.get(key).orEmpty()
                }
            }
        }
    }

    fun updateName(name: String) = _name.update { name }

    fun storeToDataStore() {
        viewModelScope.launch {
            dataStore.updateData {
                it.toMutablePreferences().apply {
                    set(key, name.value)
                }
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Define a Koin Module in**commonMain **which provides a AppViewModel instance for dependency injection.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;val viewModelModule = module {
    viewModel { AppViewModel(get()) } 
// automatically injecting the required parameters using get()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Update our **initKoin **function:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fun initKoin(
    config: (KoinApplication.() -&amp;gt; Unit)? = null
) {
    startKoin {
        config?.invoke(this)

        modules(viewModelModule, dataStoreModule) // add viewModelModule
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;UI Layer (Shared UI with Jetpack Compose)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Inject AppViewModel in Composable App():&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import org.jetbrains.compose.ui.tooling.preview.Preview
import org.koin.compose.KoinContext
import org.koin.compose.viewmodel.koinViewModel
import org.koin.core.annotation.KoinExperimentalAPI

@OptIn(KoinExperimentalAPI::class)
@Composable
@Preview
fun App() {
    MaterialTheme {
        KoinContext {
            val viewModel = koinViewModel&amp;lt;AppViewModel&amp;gt;()

            val name by viewModel.name.collectAsStateWithLifecycle()

            Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
                Column(
                    modifier = Modifier.padding(16.dp)
                ) {
                    TextField(
                        value = name,
                        onValueChange = viewModel::updateName,
                        label = { Text("Name") }
                    )

                    Button(onClick = viewModel::storeToDataStore, modifier = Modifier.padding(top = 8.dp)) {
                        Text("Store")
                    }
                }
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Voila! 🎉 The Preferences DataStore is now fully set up and ready to use in both Android and iOS. Now, let’s test it in action! Below is video showcasing how it works on both platforms.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgests5lemqy1khww9db7.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgests5lemqy1khww9db7.gif" alt="Android API 35" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbgrwkw3cmhjtc7e49i8z.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbgrwkw3cmhjtc7e49i8z.gif" alt="Simulator 18.3.1" width="352" height="764"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Want to see the full implementation? The complete code for this tutorial is available on my GitHub. Feel free to explore and try it out!&lt;br&gt;
&lt;a href="https://github.com/rendyadidarma/DataStoreComposeMultiPlatform" rel="noopener noreferrer"&gt;&lt;strong&gt;GitHub - rendyadidarma/DataStoreComposeMultiPlatform: A Compose multiplatform project that…&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;💬 &lt;strong&gt;Let’s stay connected!&lt;/strong&gt; If you found this tutorial helpful, feel free to reach out and connect with me on &lt;a href="https://www.linkedin.com/in/rendy-adidarma/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; or &lt;a href="https://github.com/rendyadidarma" rel="noopener noreferrer"&gt;Github&lt;/a&gt;&lt;/p&gt;

</description>
      <category>kotlin</category>
      <category>composemultiplatform</category>
      <category>android</category>
      <category>ios</category>
    </item>
  </channel>
</rss>
