Simple Preferences & Proto DataStore Demo App
Tags: android, kotlin, beginners
Published: 2023-06-17 00:00:00

**Beginner's friendly step-by-step guide to learn how to use Preferences and Proto DataStore, Room Database is not covered.**

abc.com

There are a few ways you can store data locally on your Android devices:

* SharedPreferences (replaced by Preferences DataStore)
    
* **Preferences DataStore**
    
* **Proto DataStore**
    
* **Room Database**
    

SharedPreferences is the old way, which has been replaced by Preferences DataStore due to its shortcomings (e.g. not safe to be called from the UI thread).

In this article, I'm going to share how to store data using both Preferences DataStore and Proto DataStore. Room Database will be covered later in the next article.

## Prefer which way to store data?

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1683951081731/4a6e6539-0aad-41f0-b107-70c583ff9ad1.png)

**Preferences DataStore** stores data in a **simple key-value format.** It is usually for a small and simple dataset. If you have a more complex data format, use Proto DataStore.

**Proto DataStore** stores data in **custom data type** format, which is defined using **protocol buffers**. This is usually for a medium size dataset and it doesn't support partial updates. So for a very large dataset, it is better to use the Room Database.

**Room Database** is similar to Proto DataStore which can store data in **custom data type** in a **relational database** (SQLite) format. Since it supports partial updates, so there won't be any problem storing a very large and complex Database.

## Preferences DataStore

Here is a simple app example to store 2 setting values in preferences DataStore. One is Boolean format and another is Int format.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1684212451881/2616b0e0-748f-4ce6-8e1c-24bbee6263e2.png)

### 1\. Add Preferences DataStore Library

```kotlin
dependencies {
    implementation("androidx.datastore:datastore-preferences:1.0.0")
}
```

### 2\. Create a Preferences DataStore

It can be created anywhere and usually, you can do it at the file level of your main activity.

```kotlin
val Context.prefsDataStore by preferencesDataStore(name = "settings")
```

You can also create more than one DataStore but make sure you give it a unique name.

```kotlin
val Context.prefsDataStore1 by preferencesDataStore(name = "settings1")
val Context.prefsDataStore2 by preferencesDataStore(name = "settings2")
val Context.prefsDataStore3 by preferencesDataStore(name = "settings3")
```

### 3\. Access Preferences DataStore in Composable

To access the DataStore in Composable, you can pass it in from your activity as a parameter or simply use the `LocalContext` [CompositionLocal](https://vtsen.hashnode.dev/pass-by-value-vs-compositionlocal-vs-static-compositionlocal#heading-compositionlocal) which I prefer.

```kotlin
@Composable
fun YourComposable() {
    val dataStore = LocalContext.current.prefsDataStore
}
```

### 4\. Setup the Preferences Key and Name

Before we can use Preferences DataStore(read from it and write to it), you need to set up the preferences key and name.

This creates `booleanPreferencesKey()` which allows you to read from / write to the DataStore.

```kotlin
 private val booleanOptionName = "Boolean Option"
 private val booleanOptionKey = booleanPreferencesKey(booleanOptionName)
```

Here is the list of supported preferences key data types:

* `intPreferencesKey`
    
* `doublePreferencesKey`
    
* `stringPreferencesKey`
    
* `booleanPreferencesKey`
    
* `floatPreferencesKey`
    
* `longPreferencesKey`
    
* `stringSetPreferencesKey`
    

> If these data types do not meet your needs, Proto DataStore is probably what you need.

### 5\. Write to a Preferences DataStore

This is what I have in my view model. To write to a preferences DataStore, you use the `DataStore<Preferences>.edit()` suspend API. Since it is a suspend function, you need to launch it under a [coroutine](https://vtsen.hashnode.dev/kotlin-coroutines-basics-simple-android-app-demo).

```kotlin
class PrefsDataStoreScreenViewModel(
    private val dataStore: DataStore<Preferences>
) : ViewModel() {
    /*...*/
    private val booleanOptionName = "Boolean Option"
    private val booleanOptionKey = booleanPreferencesKey(booleanOptionName)

    fun saveBooleanOptionValue(value: Boolean) {
        viewModelScope.launch {
            dataStore.edit { preferences ->
                preferences[booleanOptionKey] = value
            }
        }
    }
}
```

The preferences here is the `MutablePreferences` very much behave the same as [MutableMap in Kotlin](https://vtsen.hashnode.dev/complete-c-to-kotlin-syntax-comparisons#heading-ireadonlydictionary-dictionary-vs-map-mutablemap) which you can assign key-value pairs.

### 6\. Read from a Preferences DataStore

To read, we use `DataStore<Preferences>.data` flow API which exposes `Flow<Preferences>`

Typically, I don't want to expose the flow directly in ViewModel. I prefer to expose the `StateFlow` instead because I don't want the flow to keep emitting whenever there is a new collector.

```kotlin
class PrefsDataStoreScreenViewModel(
    private val dataStore: DataStore<Preferences>
) : ViewModel() {

    private val booleanOptionName = "Boolean Option"
    private val booleanOptionKey = booleanPreferencesKey(booleanOptionName)

    val booleanOptionState = dataStore.data.map { preferences ->
            preferences[booleanOptionKey] ?: false
        }.stateIn(
            scope = viewModelScope,
            started = SharingStarted.Eagerly,
            initialValue = false
        )
    /*...*/
}
```

It looks complex, but let me break it out.

This converts `Flow<Preferences>` to `Flow<Boolean>` using `Flow<T>.map()` API. When the value is null, it returns false since we don't want the nullable Boolean (e.g. `Flow<Boolean?>`)

```kotlin
val booleanFlow = dataStore.data.map { preferences ->
    preferences[booleanOptionKey] ?: false
}
```

Then, we convert `Flow<Boolean>` to `StateFlow<Boolean>` using the `Flow<T>.stateIn()` API.

```kotlin
val booleanStateFlow = booleanFlow.stateIn(
    scope = viewModelScope,
    started = SharingStarted.Eagerly,
    initialValue = false
)
```

To collect the StateFlow in composable function, you can use `collectAsStateWithLifecycle()`.

```kotlin
@Composable
fun PrefsDataStoreScreen(doneCallback: ()->Unit) {
    /*...*/
    val booleanOptionValue by 
        viewModel.booleanOptionState.collectAsStateWithLifecycle()
    /*...*/
}
```

If you have trouble understanding this, you may want to refer to this article.

- [Convert Flow to SharedFlow and StateFlow](https://vtsen.hashnode.dev/convert-flow-to-sharedflow-and-stateflow) 

For the rest, please refer to the source code. The code also uses a custom ViewModelFactory. If you're not familiar with it, you can read this article.

- [View Model Creation in Jetpack Compose](https://vtsen.hashnode.dev/view-model-creation-in-jetpack-compose) 

Now, let's move on to Proto DataStore...

## Proto DataStore

I implemented the bookmarked articles for my [RSS feed reader app](https://vtsen.hashnode.dev/simple-rss-feed-reader-jetpack-compose) using Proto DataStore. So, I'm going to use it as an example here, which is to store a list of bookmarked articles.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1684492215101/9ac04836-3965-4be4-b612-929674458f18.png)

### 1\. Add DataStore & protobuf-javalite Libraries

```kotlin
dependencies {
    implementation("androidx.datastore:datastore:1.0.0")
    implementation("com.google.protobuf:protobuf-javalite:3.21.12")
}
```

> Setting up DataStore is straightforward, but not the protocol buffers library and plugin. The official documentation has missed this information, so I have to figure it out by myself.

### 2\. Add protobuf Plugins

```kotlin
plugins {
    /*...*/
    id ("com.google.protobuf") version("0.9.0")
}
```

### 3\. Add Java Protobuf-lite code Generation

Add the following code at the end of your app-level build.gradle/kts file.

**Groovy**

```kotlin
plugins {
    /*...*/
    id "com.google.protobuf" version '0.9.0'
}

android {
    /*...*/
}

dependencies {
    /*...*/
}

protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:3.19.4"
    }

    generateProtoTasks {
        all().each { task ->
            task.builtins {
                java {
                    option 'lite'
                }
            }
        }
    }
}
```

**Kotlin (KTS)**

```kotlin
plugins {
    /*...*/
    id ("com.google.protobuf") version("0.9.0")
}

android {
    /*...*/
}

dependencies {
    /*...*/
}

protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:3.19.4"
    }

    generateProtoTasks {
        all().forEach { task ->
            task.builtins {
                create("java") {
                    option("lite")
                }
            }
        }
    }
}
```

> I added both Groovy and Kotlin versions here because there are not exactly 1-to-1 matches. I have issues converting from Groovy to Kotlin.
> 
> * all().each is Groovy needs to be changed to all().forEach in Kotlin/KTS
>     
> * Java's option in Groovy is not recognized by Kotlin/KTS.
>     

### 4\. Define a ProtoBuf Scheme

The data structure format that you want to store is defined in this ProtoBuf scheme file (**\*.proto**) and you need to create this proto file in **app/src/main/proto/** directory in your app module.

In this example, I would like to store the list of bookmarked article IDs in string format and here is the **ProtoPreferences.proto** file.

```kotlin
syntax = "proto3";

option java_package = "vtsen.hashnode.dev.datastoredemoapp";
option java_multiple_files = true;

message ProtoPreferences {
  //first field 
  map<string, bool> bookmarked_article_ids = 1;
  //second field
  map<string, bool> read_article_ids = 2;
}
```

* `syntax="proto3"` indicates the definition follows proto3 version rules
    
* `java_package` refers to the `applicationId` in your build.gradle.kts file.
    
* `java_multiple_files = true` configure each message has it's own Java class file.
    
* `message ProtoPreferences` is a message type that has 2 fields in the above example.
    
* `map<string, bool>` is the map data structure where the key is the article Id in string format and the value is a boolean that indicates whether the article is bookmarked.
    

> **Tip:** If you name your proto file name the same as the message name (e.g. ProtoPreferences), make sure you use the same capitalization. For example, protopreferences.proto won't work. It must be ProtoPreferences.proto or some other name.

Compile the code, make sure you get the successful build and the `ProtoPreferences` Java class should be generated.

### 5\. Implement DataStore Serializer Interface

Implement the `Serializer` interface [singleton object](https://vtsen.hashnode.dev/how-to-create-singleton-class-in-kotlin#heading-object-declaration-singleton) for generated `ProtoPreferences` Java class (ProtoBuf scheme) that you created in step 4 above.

```kotlin
object ProtoPreferencesSerializer : Serializer<ProtoPreferences> {
    override val defaultValue: ProtoPreferences
        = ProtoPreferences.getDefaultInstance()

    override suspend fun readFrom(
        input: InputStream
    ): ProtoPreferences{
        try {
            return ProtoPreferences.parseFrom(input)
        } catch (exception: InvalidProtocolBufferException) {
            throw CorruptionException("Cannot read proto.", exception)
        }
    }

    override suspend fun writeTo(
        t: ProtoPreferences, 
        output: OutputStream) = t.writeTo(output)
}
```

This is pretty standard code to read from and write to the serializer, so I'm not going to explain it here.

### 6\. Create a Proto DataStore

Similar to Preferences DataStore, instead of using `by preferenceDataStore` delegate, you use `by dataStore`

```kotlin
val Context.protoDataStore: DataStore<ProtoPreferences> by dataStore(
    fileName = "ProtoPreferences.pb",
    serializer = ProtoPreferencesSerializer
)
```

In this example, it is created in the main activity. Depending on your need, you can also create it in the repository class if it is only referenced there. You can see the example [here](https://github.com/vinchamp77/AndroidNews/blob/master/app/src/main/java/vtsen/hashnode/dev/androidnews/data/repository/UserPreferencesRepositoryImpl.kt).

### 7\. Write to a Proto DataStore

`DataStore<T>.updateData()` suspend function is used to update the Proto DataStore data. It needs a builder to build a `MessageType`as you can see below.

```kotlin
class ProtoDataStoreScreenViewModel(
    private val dataStore: DataStore<ProtoPreferences>
) : ViewModel() {
    /*...*/    
    fun saveBookmarkedArticle(articleId: String, bookmarked: Boolean) {
        viewModelScope.launch {
            dataStore.updateData { protoPreferences ->
                protoPreferences.toBuilder()
                    .putBookmarkedArticleIds(articleId, bookmarked)
                    .build()
            }
        }
    }
    /*...*/
}
```

* `putBookmarkedArticleIds()` API is auto-generated based on what you define in the ProtoBuf scheme. For other generated APIs, refer to the generated ProtoPreferences.java file.
    

### 8\. Read from a Proto DataStore

Reading is very similar to Preferences DataStore.

`DataStore<ProtoPreferences>.data` flow API exposes `Flow<ProtoPreferences>`. Then, it is converted to `Flow<Map<String, Boolean>>` using flow mapping. Finally, it is converted to `StateFlow<Map<String, Boolean>>` using stateIn flow operator.

```kotlin
class ProtoDataStoreScreenViewModel(
    private val dataStore: DataStore<ProtoPreferences>
) : ViewModel() {
    /*...*/
    val bookmarkedArticlesState = dataStore.data.map { protoPreferences ->
            protoPreferences.bookmarkedArticleIdsMap
        }.stateIn(
            scope = viewModelScope,
            started = SharingStarted.Eagerly,
            initialValue = null)
}
```

Here is the usage which you are probably already familiar with.

```kotlin
@Composable
fun ProtoDataStoreScreen(doneCallback: ()->Unit) {

    val viewModel: ProtoDataStoreScreenViewModel = viewModel(
        factory = ViewModelFactory(LocalContext.current.protoDataStore)
    )

    val articles by viewModel.bookmarkedArticlesState
        .collectAsStateWithLifecycle()

    /*...*/
}
```

## Conclusion

Preferences DataStore is relatively easy to implement with comprehensive documentation available. However, setting up Proto DataStore can be a bit challenging due to the lack of proper documentation.

To address this, I have taken the initiative to document the process here, providing a valuable reference for anyone encountering similar difficulties.

## Source Code

GitHub Repository: [Demo\_DataStore](https://github.com/vinchamp77/Demo_DataStore)

---

*Originally published at [https://vtsen.hashnode.dev](https://vtsen.hashnode.dev/simple-preferences-proto-datastore-demo-app)*.