Skip to content

Commit

Permalink
Use multiplatform DataStore version 1.1.0-dev01
Browse files Browse the repository at this point in the history
  • Loading branch information
russhwolf committed Oct 7, 2022
1 parent 772c49f commit 3d2cd16
Show file tree
Hide file tree
Showing 9 changed files with 368 additions and 71 deletions.
2 changes: 1 addition & 1 deletion buildSrc/src/main/kotlin/Versions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ object Versions {
const val minSdk = 15
const val compileSdk = 33

const val androidxDatastore = "1.0.0"
const val androidxDatastore = "1.1.0-dev01"
const val androidxPreference = "1.2.0"
const val androidxStartup = "1.1.1"
const val androidxTest = "1.4.0"
Expand Down
18 changes: 12 additions & 6 deletions multiplatform-settings-datastore/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,20 @@ plugins {
kotlin("multiplatform")
id("com.android.library")
id("org.jetbrains.dokka")
id("org.jetbrains.kotlin.plugin.serialization") version Versions.serializationPlugin
`maven-publish`
signing
}

standardConfiguration(
"android",
"jvm"
"iosArm64",
"iosSimulatorArm64",
"iosX64",
"jvm",
"linuxX64",
"macosArm64",
"macosX64"
)

kotlin {
Expand All @@ -39,6 +46,8 @@ kotlin {
implementation(project(":multiplatform-settings-coroutines"))

implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.coroutines}")

implementation("androidx.datastore:datastore-preferences-core:${Versions.androidxDatastore}")
}
}
val commonTest by getting {
Expand All @@ -51,11 +60,8 @@ kotlin {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:${Versions.coroutines}")

implementation("app.cash.turbine:turbine:${Versions.turbine}")
}
}
val jvmCommonMain by getting {
dependencies {
implementation("androidx.datastore:datastore-preferences-core:${Versions.androidxDatastore}")
implementation("com.squareup.okio:okio-fakefilesystem:3.2.0")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
}
}
val jvmCommonTest by getting {
Expand Down
18 changes: 18 additions & 0 deletions multiplatform-settings-datastore/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#
# Copyright 2022 Russell Wolf
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Because DataStore doesn't have an implementation for all platforms, we can't define it in common
# for legacy metadata
kotlin.mpp.enableCompatibilityMetadataVariant=false

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@

package com.russhwolf.settings.datastore

import androidx.datastore.core.DataStore
import androidx.datastore.core.okio.OkioSerializer
import androidx.datastore.core.okio.OkioStorage
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.intPreferencesKey
Expand All @@ -29,20 +31,28 @@ import com.russhwolf.settings.Settings
import com.russhwolf.settings.coroutines.toBlockingObservableSettings
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.runBlocking
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import okio.FileSystem
import okio.Path.Companion.toPath
import okio.fakefilesystem.FakeFileSystem
import kotlin.test.AfterTest
import kotlin.test.Test
import kotlin.test.assertEquals

private val temporaryFolder = TemporaryFolder()
expect val preferencesSerializer: OkioSerializer<Preferences>

// Shim to work around IDE issues. HMPP doesn't support JVM/Android so we can't see java.io.File here.
internal expect fun TemporaryFolder.createDataStore(fileName: String): DataStore<Preferences>
private var scope: CoroutineScope = CoroutineScope(SupervisorJob())

private val fakeFileSystem = FakeFileSystem()

private val factory = object : Settings.Factory {
override fun create(name: String?): Settings {
val dataStore: DataStore<Preferences> = temporaryFolder.createDataStore("$name.preferences_pb")
val storage = OkioStorage(fakeFileSystem, preferencesSerializer) {
FileSystem.SYSTEM_TEMPORARY_DIRECTORY / "${name ?: "settings"}.preferences_pb".toPath()
}
val dataStore = PreferenceDataStoreFactory.create(storage, scope = scope)
val settings = DataStoreSettings(dataStore)
return settings.toBlockingObservableSettings(CoroutineScope(Dispatchers.Unconfined))
}
Expand All @@ -53,13 +63,20 @@ class DataStoreSettingsTest : BaseSettingsTest(
allowsDuplicateInstances = false,
hasListeners = true
) {

@Rule
fun temporaryFolder() = temporaryFolder
@AfterTest
fun tearDown() {
scope.cancel()
scope = CoroutineScope(SupervisorJob())
fakeFileSystem.checkNoOpenFiles()
}

@Test
fun constructor_datastore(): Unit = runBlocking {
val dataStore = temporaryFolder.createDataStore("settings.preferences_pb")
val storage = OkioStorage(fakeFileSystem, preferencesSerializer) {
FileSystem.SYSTEM_TEMPORARY_DIRECTORY / "test.preferences_pb".toPath()
}
val dataStore = PreferenceDataStoreFactory.create(storage, scope = scope)

val settings = DataStoreSettings(dataStore)

dataStore.edit { it[intPreferencesKey("a")] = 3 }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* Copyright 2022 Russell Wolf
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.russhwolf.settings.datastore

import androidx.datastore.core.CorruptionException
import androidx.datastore.core.okio.OkioSerializer
import androidx.datastore.preferences.PreferencesMapCompat
import androidx.datastore.preferences.PreferencesProto.PreferenceMap
import androidx.datastore.preferences.PreferencesProto.StringSet
import androidx.datastore.preferences.PreferencesProto.Value
import androidx.datastore.preferences.core.MutablePreferences
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.byteArrayPreferencesKey
import androidx.datastore.preferences.core.doublePreferencesKey
import androidx.datastore.preferences.core.emptyPreferences
import androidx.datastore.preferences.core.floatPreferencesKey
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.longPreferencesKey
import androidx.datastore.preferences.core.mutablePreferencesOf
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.core.stringSetPreferencesKey
import androidx.datastore.preferences.protobuf.ByteString
import okio.BufferedSink
import okio.BufferedSource
import okio.IOException

actual val preferencesSerializer: OkioSerializer<Preferences> = PreferencesSerializer

/**
* Proto based serializer for Preferences.
*/
internal object PreferencesSerializer : OkioSerializer<Preferences> {
val fileExtension = "preferences_pb"

override val defaultValue: Preferences
get() {
return emptyPreferences()
}

@Throws(IOException::class, CorruptionException::class)
override suspend fun readFrom(source: BufferedSource): Preferences {
val preferencesProto = PreferencesMapCompat.readFrom(source.inputStream())

val mutablePreferences = mutablePreferencesOf()

preferencesProto.preferencesMap.forEach { (name, value) ->
addProtoEntryToPreferences(name, value, mutablePreferences)
}

return mutablePreferences.toPreferences()
}

@Throws(IOException::class, CorruptionException::class)
override suspend fun writeTo(t: Preferences, sink: BufferedSink) {
val preferences = t.asMap()
val protoBuilder = PreferenceMap.newBuilder()

for ((key, value) in preferences) {
protoBuilder.putPreferences(key.name, getValueProto(value))
}

protoBuilder.build().writeTo(sink.outputStream())
}

private fun getValueProto(value: Any): Value {
return when (value) {
is Boolean -> Value.newBuilder().setBoolean(value).build()
is Float -> Value.newBuilder().setFloat(value).build()
is Double -> Value.newBuilder().setDouble(value).build()
is Int -> Value.newBuilder().setInteger(value).build()
is Long -> Value.newBuilder().setLong(value).build()
is String -> Value.newBuilder().setString(value).build()
is Set<*> ->
@Suppress("UNCHECKED_CAST")
Value.newBuilder().setStringSet(
StringSet.newBuilder().addAllStrings(value as Set<String>)
).build()

is ByteArray -> Value.newBuilder().setBytes(ByteString.copyFrom(value)).build()
else -> throw IllegalStateException(
"PreferencesSerializer does not support type: ${value.javaClass.name}"
)
}
}

private fun addProtoEntryToPreferences(
name: String,
value: Value,
mutablePreferences: MutablePreferences
) {
return when (value.valueCase) {
Value.ValueCase.BOOLEAN ->
mutablePreferences[booleanPreferencesKey(name)] =
value.boolean

Value.ValueCase.FLOAT -> mutablePreferences[floatPreferencesKey(name)] = value.float
Value.ValueCase.DOUBLE -> mutablePreferences[doublePreferencesKey(name)] = value.double
Value.ValueCase.INTEGER -> mutablePreferences[intPreferencesKey(name)] = value.integer
Value.ValueCase.LONG -> mutablePreferences[longPreferencesKey(name)] = value.long
Value.ValueCase.STRING -> mutablePreferences[stringPreferencesKey(name)] = value.string
Value.ValueCase.STRING_SET ->
mutablePreferences[stringSetPreferencesKey(name)] =
value.stringSet.stringsList.toSet()

Value.ValueCase.BYTES ->
mutablePreferences[byteArrayPreferencesKey(name)] = value.bytes.toByteArray()

Value.ValueCase.VALUE_NOT_SET ->
throw CorruptionException("Value not set.")

null -> throw CorruptionException("Value case is null.")
}
}
}

This file was deleted.

Loading

0 comments on commit 3d2cd16

Please sign in to comment.