diff --git a/README.md b/README.md index 530f808..5695144 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,19 @@ # Krate -[![Build Status](https://app.bitrise.io/app/40d6bd22db4cfda8/status.svg?token=0neqv73n3TXp9F0nNxj_rA&branch=main)](https://app.bitrise.io/app/40d6bd22db4cfda8) +![Build Status](https://github.com/AutSoft/Krate/workflows/Build%20and%20test/badge.svg?branch=main) ![Krate banner](./docs/krate.png) -_Krate_ is a `SharedPreferences` wrapper library that uses delegated properties for convenient access to `SharedPreferences` values. +**Krate** is a `SharedPreferences` wrapper library that uses delegated properties for convenient access to `SharedPreferences` values. Here's what its basic usage looks like, extending the provided `SimpleKrate` class: ```kotlin class UserSettings(context: Context) : SimpleKrate(context) { - var notificationsEnabled by booleanPref("notifications_enabled", false) - var loginCount by intPref("login_count", 0) - var nickname by stringPref("nickname", "") + var notificationsEnabled by booleanPref().withDefault(false) + var loginCount by intPref().withDefault(0) + var nickname by stringPref() } @@ -24,37 +24,61 @@ Log.d("LOGIN_COUNT", "Count: ${settings.loginCount}") # Dependency -You can include _Krate_ in your project from the `mavenCentral()` repository, like so: +Krate is available from `mavenCentral()`. You can add it to your dependencies with the following line: ```groovy -implementation 'hu.autsoft:krate:1.2.0' +implementation 'hu.autsoft:krate:2.0.0' ``` -# Optionals vs defaults +# Basics -Each stored property can be declared with or without a default value. Here's how the two options differ: +A Krate property is nullable by default. It will have a `null` value if no value has been set for the property yet, and its current value can be erased from `SharedPreferences` completely by setting it to `null`. -### Optional values: +```kotlin +var username: String? by stringPref() +``` + +### Default values -A property declared with the one-argument delegate function will have a nullable type. It will have a `null` value if no value has been set for this property yet, and its current value can be erased from `SharedPreferences` completely by setting it to `null`. +You can provide a default value for the property by chaining a `withDefault` call on the delegate function. This will give the property a non-nullable type. ```kotlin -var username: String? by stringPref("username") +var username: String by stringPref().withDefault("admin") ``` -### Default values: +Reading from this property will return either the value it was last set to, or the default value if it's never been set. + +> Note that there's no way to remove these values from `SharedPreferences` (although you could set it explicitly to the default value). + +### Custom keys + +By default, the the property will be stored under the key of the property's name in the underlying `SharedPreferences` instance. -A property declared with the two-argument delegate function takes its default value as the second argument, and it will have a non-nullable type. Reading from this property will return either the value it was last set to or the default value. Setting this property will update the value stored in `SharedPreferences`. Note that there's no way to remove these values from `SharedPreferences` (although you could set it explicitly to the default value). +You can change this behaviour by explicitly providing the key as an argument: ```kotlin -var username: String by stringPref("username", defaultValue = "admin") +var username: String? by stringPref(key = "USER_NAME") ``` +> Note that if you rely on property names as keys, renaming a Krate property will become a breaking change, and the previously stored value will be lost. This can be avoided by adding an explicit key with the name of the original property. + +### Validation + +You can add validation rules to your Krate properties by calling `validate` on any of Krate's delegate functions: + +```kotlin +var percentage: Int by intPref() + .withDefault(0) + .validate { it in 0..100 } +``` + +If this validation fails, an `IllegalArgumentException` will be thrown. + # Custom Krate implementations -You can usually get away with extending `SimpleKrate`, as it does allow you to pass in a custom name for the `SharedPreferences` to be used to store your values in its constructor as an optional parameter. (If you pass in no `name` parameter to its constructor, it will default to using the instance returned by `PreferenceManager.getDefaultSharedPreferences(context)`.) +You can usually get away with extending `SimpleKrate`, as it does allow you to pass in a custom name for the `SharedPreferences` to be used to store your values in its constructor as an optional parameter. (If you pass in no `name` parameter to its constructor, it will default to using the instance returned by `PreferenceManager.getDefaultSharedPreferences(context)`.) -However, you can also implement the `Krate` interface directly if you want to manage the `SharedPreferences` instance yourself for whatever reason - all this interface requires is a property that holds a `SharedPreferences` instance. With that, you can use the delegate functions the same way as shown above: +However, you can also implement the `Krate` interface directly if you want to manage the `SharedPreferences` instance yourself for whatever reason - all this interface requires is a property that holds a `SharedPreferences` instance. With that, you can use the delegate functions the same way as shown above: ```kotlin class ExampleCustomKrate(context: Context) : Krate { @@ -65,7 +89,7 @@ class ExampleCustomKrate(context: Context) : Krate { sharedPreferences = context.applicationContext.getSharedPreferences("custom_krate_prefs", Context.MODE_PRIVATE) } - var exampleBoolean by booleanPref("exampleBoolean", false) + var exampleBoolean by booleanPref().withDefault(false) } ``` @@ -79,7 +103,7 @@ class MainActivity : AppCompatActivity(), Krate { getPreferences(Context.MODE_PRIVATE) // Could also fetch a named or default SharedPrefs } - var username by stringPref("username", "") + var username by stringPref().withDefault("") } ``` @@ -99,24 +123,11 @@ class EncryptedKrate(applicationContext: Context) : Krate { sharedPreferences = EncryptedSharedPreferences.create(applicationContext, ...) } - val myStringValue: String by stringPref("my_string_value", "") + val myStringValue: String by stringPref().withDefault("") } ``` -# Validation - -You can add validation rules to your Krate properties by calling `validate` on any of Krate's delegate functions: - -```kotlin -var percentage: Int by intPref( - key = "percentage", - defaultValue = 0, -).validate { it in 0..100 } -``` - -If this validation fails, an `IllegalArgumentException` will be thrown. - -# Addons +# Serialization addons Krate, by default, supports the types that `SharedPreferences` supports. These are `Boolean`, `Float`, `Int`, `Long`, `String` and `Set`. You may of course want to store additional types in Krate. @@ -132,8 +143,8 @@ The usage of the Krate integration is the same for both setups: ```kotlin class MoshiKrate(context: Context) : SimpleKrate(context) { - var user: User? by moshiPref("user") - var savedArticles: List
? by moshiPref("articles") + var user: User? by moshiPref() + var savedArticles: List
? by moshiPref() } ``` @@ -152,7 +163,7 @@ class CustomMoshiKrate(context: Context) : SimpleKrate(context) { If you only want to use Moshi adapters that you generate via Moshi's [codegen facilities](https://github.com/square/moshi#codegen), you can use the following Krate artifact in your project to make use of these adapters: ```groovy -implementation 'hu.autsoft:krate-moshi-codegen:1.2.0' +implementation 'hu.autsoft:krate-moshi-codegen:2.0.0' ``` This will give you a default `Moshi` instance created by a call to `Moshi.Builder().build()`. This instance will find and use any of the adapters generated by Moshi's codegen automatically. @@ -162,7 +173,7 @@ This will give you a default `Moshi` instance created by a call to `Moshi.Builde If you rely on [reflection](https://github.com/square/moshi#reflection) for your Moshi serialization, and therefore need a `KotlinJsonAdapterFactory` included in your `Moshi` instance, use the following Krate Moshi dependency: ```groovy -implementation 'hu.autsoft:krate-moshi-reflect:1.2.0' +implementation 'hu.autsoft:krate-moshi-reflect:2.0.0' ``` The default `Moshi` instance from this dependency will include the aforementioned factory, and be able to serialize any Kotlin class. Note that this approach relies on the `kotlin-reflect` library, which is a large dependency. @@ -174,15 +185,15 @@ You may choose to use Moshi's codegen for some classes in your project, and seri The `krate-kotlinx` artifact provides a `kotlinxPref` delegate which can store any arbitrary type, as long as Kotlinx.serializazion can serialize and deserialize it. This addon, like the base library, is available from `mavenCentral()`: ```groovy -implementation 'hu.autsoft:krate-kotlinx:1.2.0' +implementation 'hu.autsoft:krate-kotlinx:2.0.0' ``` Its usage is the same as with any of the base library's delegates: ```kotlin class KotlinxKrate(context: Context) : SimpleKrate(context) { - var user: User? by kotlinxPref("user") - var savedArticles: List
? by kotlinxPref("articles") + var user: User? by kotlinxPref() + var savedArticles: List
? by kotlinxPref() } ``` @@ -197,7 +208,7 @@ class CustomKotlinxKrate(context: Context) : SimpleKrate(context) { } } - var user: User? by kotlinxPref("user") + var user: User? by kotlinxPref() } ``` @@ -206,15 +217,15 @@ class CustomKotlinxKrate(context: Context) : SimpleKrate(context) { The `krate-gson` artifact provides a `gsonPref` delegate which can store any arbitrary type, as long as Gson can serialize and deserialize it. This addon, like the base library, is available from `mavenCentral()`: ```groovy -implementation 'hu.autsoft:krate-gson:1.2.0' +implementation 'hu.autsoft:krate-gson:2.0.0' ``` Its basic usage is the same as with any of the base library's delegates: ```kotlin class GsonKrate(context: Context) : SimpleKrate(context) { - var user: User? by gsonPref("user") - var savedArticles: List
? by gsonPref("articles") + var user: User? by gsonPref() + var savedArticles: List
? by gsonPref() } ``` @@ -226,7 +237,7 @@ class CustomGsonKrate(context: Context) : SimpleKrate(context) { gson = GsonBuilder().create() } - var user: User? by gsonPref("user") + var user: User? by gsonPref() } ``` diff --git a/app/build.gradle b/app/build.gradle index 3d85abb..485d658 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,5 +1,9 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' +plugins { + id 'com.android.application' + id 'kotlin-android' + id 'kotlin-kapt' + id 'org.jetbrains.kotlin.plugin.serialization' +} android { compileSdkVersion compile_sdk @@ -22,14 +26,28 @@ android { } dependencies { + + // Krate + implementation project(path: ':krate') + implementation project(path: ':krate-gson') + implementation project(path: ':krate-kotlinx') + implementation project(path: ':krate-moshi-codegen') + + // Android implementation 'com.google.android.material:material:1.4.0' - implementation 'androidx.appcompat:appcompat:1.3.1' - implementation 'androidx.constraintlayout:constraintlayout:2.1.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.2' + + // KotlinX + implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.1' + + // Moshi + def moshiVersion = "1.12.0" + implementation "com.squareup.moshi:moshi-kotlin:$moshiVersion" + kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshiVersion" + implementation "com.squareup.moshi:moshi-adapters:$moshiVersion" // Third-party SharedPreferences implementations implementation "androidx.security:security-crypto:1.1.0-alpha03" implementation 'com.frybits.harmony:harmony:1.1.2' - - implementation project(path: ':krate') - implementation project(path: ':krate-gson') } diff --git a/app/src/main/java/hu/autsoft/krateexample/ExampleActivity.kt b/app/src/main/java/hu/autsoft/krateexample/ExampleActivity.kt index 7af8757..cde0b88 100644 --- a/app/src/main/java/hu/autsoft/krateexample/ExampleActivity.kt +++ b/app/src/main/java/hu/autsoft/krateexample/ExampleActivity.kt @@ -6,6 +6,7 @@ import hu.autsoft.krateexample.databinding.ActivityExampleBinding import hu.autsoft.krateexample.krates.ExampleCustomKrate import hu.autsoft.krateexample.krates.ExampleSettings import hu.autsoft.krateexample.krates.ExampleSimpleKrate +import hu.autsoft.krateexample.models.User class ExampleActivity : AppCompatActivity() { @@ -47,6 +48,12 @@ class ExampleActivity : AppCompatActivity() { binding.longPreferenceInput.setText(exampleSettings.exampleLong.toString()) binding.stringPreferenceInput.setText(exampleSettings.exampleString) binding.stringSetPreferenceInput.setText(exampleSettings.exampleStringSet.joinToString(separator = ", ")) + binding.gsonPreferenceFirstInput.setText(exampleSettings.exampleUserGson.firstName) + binding.gsonPreferenceLastInput.setText(exampleSettings.exampleUserGson.lastName) + binding.kotlinxPreferenceFirstInput.setText(exampleSettings.exampleUserKotlinX.firstName) + binding.kotlinxPreferenceLastInput.setText(exampleSettings.exampleUserKotlinX.lastName) + binding.moshiPreferenceFirstInput.setText(exampleSettings.exampleUserMoshi.firstName) + binding.moshiPreferenceLastInput.setText(exampleSettings.exampleUserMoshi.lastName) } override fun onPause() { @@ -59,6 +66,18 @@ class ExampleActivity : AppCompatActivity() { exampleSettings.exampleString = binding.stringPreferenceInput.text.toString() exampleSettings.exampleStringSet = binding.stringSetPreferenceInput.text.toString().split(",").map(String::trim).toSet() + exampleSettings.exampleUserGson = User( + binding.gsonPreferenceFirstInput.text.toString(), + binding.gsonPreferenceLastInput.text.toString(), + ) + exampleSettings.exampleUserKotlinX = User( + binding.kotlinxPreferenceFirstInput.text.toString(), + binding.kotlinxPreferenceLastInput.text.toString(), + ) + exampleSettings.exampleUserMoshi = User( + binding.moshiPreferenceFirstInput.text.toString(), + binding.moshiPreferenceLastInput.text.toString(), + ) } } diff --git a/app/src/main/java/hu/autsoft/krateexample/krates/ExampleCustomKrate.kt b/app/src/main/java/hu/autsoft/krateexample/krates/ExampleCustomKrate.kt index ab43afa..57bd0c8 100644 --- a/app/src/main/java/hu/autsoft/krateexample/krates/ExampleCustomKrate.kt +++ b/app/src/main/java/hu/autsoft/krateexample/krates/ExampleCustomKrate.kt @@ -6,11 +6,16 @@ import android.content.Context import android.content.SharedPreferences import hu.autsoft.krate.Krate import hu.autsoft.krate.booleanPref +import hu.autsoft.krate.default.withDefault import hu.autsoft.krate.floatPref +import hu.autsoft.krate.gson.gsonPref import hu.autsoft.krate.intPref +import hu.autsoft.krate.kotlinx.kotlinxPref import hu.autsoft.krate.longPref +import hu.autsoft.krate.moshi.moshiPref import hu.autsoft.krate.stringPref import hu.autsoft.krate.stringSetPref +import hu.autsoft.krateexample.models.User class ExampleCustomKrate(context: Context) : Krate, ExampleSettings { @@ -20,11 +25,14 @@ class ExampleCustomKrate(context: Context) : Krate, ExampleSettings { sharedPreferences = context.applicationContext.getSharedPreferences("custom_krate_prefs", Context.MODE_PRIVATE) } - override var exampleBoolean by booleanPref("exampleBoolean", false) - override var exampleFloat by floatPref("exampleFloat", 0f) - override var exampleInt by intPref("exampleInt", 0) - override var exampleLong by longPref("exampleLong", 0L) - override var exampleString by stringPref("exampleString", "") - override var exampleStringSet by stringSetPref("exampleStringSet", setOf()) + override var exampleBoolean by booleanPref().withDefault(false) + override var exampleFloat by floatPref().withDefault(0f) + override var exampleInt by intPref().withDefault(0) + override var exampleLong by longPref().withDefault(0L) + override var exampleString by stringPref().withDefault("") + override var exampleStringSet by stringSetPref().withDefault(setOf()) + override var exampleUserGson by gsonPref().withDefault(User("Gson", "Green")) + override var exampleUserKotlinX by kotlinxPref().withDefault(User("KotlinX", "Klark")) + override var exampleUserMoshi by moshiPref().withDefault(User("Moshi", "Miller")) } diff --git a/app/src/main/java/hu/autsoft/krateexample/krates/ExampleSettings.kt b/app/src/main/java/hu/autsoft/krateexample/krates/ExampleSettings.kt index 1252ce7..c922892 100644 --- a/app/src/main/java/hu/autsoft/krateexample/krates/ExampleSettings.kt +++ b/app/src/main/java/hu/autsoft/krateexample/krates/ExampleSettings.kt @@ -1,5 +1,7 @@ package hu.autsoft.krateexample.krates +import hu.autsoft.krateexample.models.User + interface ExampleSettings { var exampleBoolean: Boolean var exampleFloat: Float @@ -7,4 +9,7 @@ interface ExampleSettings { var exampleLong: Long var exampleString: String var exampleStringSet: Set + var exampleUserGson: User + var exampleUserKotlinX: User + var exampleUserMoshi: User } diff --git a/app/src/main/java/hu/autsoft/krateexample/krates/ExampleSimpleKrate.kt b/app/src/main/java/hu/autsoft/krateexample/krates/ExampleSimpleKrate.kt index 62e9def..0323c7d 100644 --- a/app/src/main/java/hu/autsoft/krateexample/krates/ExampleSimpleKrate.kt +++ b/app/src/main/java/hu/autsoft/krateexample/krates/ExampleSimpleKrate.kt @@ -3,11 +3,16 @@ package hu.autsoft.krateexample.krates import android.content.Context import hu.autsoft.krate.SimpleKrate import hu.autsoft.krate.booleanPref +import hu.autsoft.krate.default.withDefault import hu.autsoft.krate.floatPref +import hu.autsoft.krate.gson.gsonPref import hu.autsoft.krate.intPref +import hu.autsoft.krate.kotlinx.kotlinxPref import hu.autsoft.krate.longPref +import hu.autsoft.krate.moshi.moshiPref import hu.autsoft.krate.stringPref import hu.autsoft.krate.stringSetPref +import hu.autsoft.krateexample.models.User class ExampleSimpleKrate(context: Context) : SimpleKrate(context, NAME), ExampleSettings { @@ -15,11 +20,13 @@ class ExampleSimpleKrate(context: Context) : SimpleKrate(context, NAME), Example private const val NAME = "example" } - override var exampleBoolean by booleanPref("exampleBoolean", false) - override var exampleFloat by floatPref("exampleFloat", 0f) - override var exampleInt by intPref("exampleInt", 0) - override var exampleLong by longPref("exampleLong", 0L) - override var exampleString by stringPref("exampleString", "") - override var exampleStringSet by stringSetPref("exampleStringSet", setOf()) - + override var exampleBoolean by booleanPref("exampleBoolean").withDefault(false) + override var exampleFloat by floatPref("exampleFloat").withDefault(0f) + override var exampleInt by intPref("exampleInt").withDefault(0) + override var exampleLong by longPref("exampleLong").withDefault(0L) + override var exampleString by stringPref("exampleString").withDefault("") + override var exampleStringSet by stringSetPref("exampleStringSet").withDefault(setOf()) + override var exampleUserGson by gsonPref("user_gson").withDefault(User("Gson", "Green")) + override var exampleUserKotlinX by kotlinxPref("user_kotlinx").withDefault(User("KotlinX", "Klark")) + override var exampleUserMoshi by moshiPref("user_moshi").withDefault(User("Moshi", "Miller")) } diff --git a/app/src/main/java/hu/autsoft/krateexample/krates/ExampleThirdPartyKrates.kt b/app/src/main/java/hu/autsoft/krateexample/krates/ExampleThirdPartyKrates.kt index fcca564..b4aa8a0 100644 --- a/app/src/main/java/hu/autsoft/krateexample/krates/ExampleThirdPartyKrates.kt +++ b/app/src/main/java/hu/autsoft/krateexample/krates/ExampleThirdPartyKrates.kt @@ -6,6 +6,7 @@ import androidx.security.crypto.EncryptedSharedPreferences import androidx.security.crypto.MasterKey import com.frybits.harmony.getHarmonySharedPreferences import hu.autsoft.krate.Krate +import hu.autsoft.krate.default.withDefault import hu.autsoft.krate.stringPref /** @@ -32,7 +33,7 @@ class EncryptedKrate(applicationContext: Context) : Krate { ) } - val myStringValue: String by stringPref("my_string_value", "") + val myStringValue: String by stringPref("my_string_value").withDefault("") } /** @@ -42,5 +43,5 @@ class EncryptedKrate(applicationContext: Context) : Krate { class HarmonyKrate(context: Context) : Krate { override val sharedPreferences: SharedPreferences = context.getHarmonySharedPreferences("PREF_NAME") - val myStringValue: String by stringPref("my_string_value", "") + val myStringValue: String by stringPref("my_string_value").withDefault("") } diff --git a/app/src/main/java/hu/autsoft/krateexample/models/User.kt b/app/src/main/java/hu/autsoft/krateexample/models/User.kt new file mode 100644 index 0000000..e2497b7 --- /dev/null +++ b/app/src/main/java/hu/autsoft/krateexample/models/User.kt @@ -0,0 +1,11 @@ +package hu.autsoft.krateexample.models + +import com.squareup.moshi.JsonClass +import kotlinx.serialization.Serializable + +@JsonClass(generateAdapter = true) +@Serializable +data class User( + val firstName: String, + val lastName: String, +) diff --git a/app/src/main/res/layout/activity_example.xml b/app/src/main/res/layout/activity_example.xml index c0bf635..ad79c41 100644 --- a/app/src/main/res/layout/activity_example.xml +++ b/app/src/main/res/layout/activity_example.xml @@ -114,6 +114,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build.gradle b/build.gradle index b8dcff9..7884631 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { ext.kotlin_version = '1.5.31' - ext.krate_version = '1.2.0' + ext.krate_version = '2.0.0' ext.moshi_version = '1.12.0' ext.junit_version = '4.13.2' @@ -17,7 +17,7 @@ buildscript { maven { url "https://plugins.gradle.org/m2/" } } dependencies { - classpath 'com.android.tools.build:gradle:7.0.2' + classpath 'com.android.tools.build:gradle:7.0.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" classpath "io.github.gradle-nexus:publish-plugin:1.1.0" diff --git a/krate-gson/src/main/kotlin/hu/autsoft/krate/gson/Functions.kt b/krate-gson/src/main/kotlin/hu/autsoft/krate/gson/Functions.kt index 6719f75..c06536d 100644 --- a/krate-gson/src/main/kotlin/hu/autsoft/krate/gson/Functions.kt +++ b/krate-gson/src/main/kotlin/hu/autsoft/krate/gson/Functions.kt @@ -4,7 +4,8 @@ package hu.autsoft.krate.gson import com.google.gson.reflect.TypeToken import hu.autsoft.krate.Krate -import hu.autsoft.krate.gson.default.GsonDelegateWithDefaultFactory +import hu.autsoft.krate.base.KeyedKratePropertyProvider +import hu.autsoft.krate.default.withDefault import hu.autsoft.krate.gson.optional.GsonDelegateFactory import java.lang.reflect.Type import kotlin.properties.PropertyDelegateProvider @@ -12,38 +13,41 @@ import kotlin.properties.ReadWriteProperty /** * Creates an optional preference of type T with the given [key] in this [Krate] instance. + * If no [key] is provided, the property's name will be used as the key. + * * This instance will be serialized using Gson. */ public inline fun Krate.gsonPref( - key: String, -): PropertyDelegateProvider> { + key: String? = null, +): KeyedKratePropertyProvider { return gsonPrefImpl(key, object : TypeToken() {}.type) } @PublishedApi internal fun Krate.gsonPrefImpl( - key: String, + key: String?, type: Type, -): PropertyDelegateProvider> { +): KeyedKratePropertyProvider { return GsonDelegateFactory(key, type) } /** * Creates a non-optional preference of type T with the given [key] and [defaultValue] in this [Krate] instance. + * If no [key] is provided, the property's name will be used as the key. + * * This instance will be serialized using Gson. */ +@Deprecated( + message = "Use .withDefault() on a gsonPref instead", + level = DeprecationLevel.ERROR, + replaceWith = ReplaceWith( + "this.gsonPref(key).withDefault(defaultValue)", + imports = arrayOf("hu.autsoft.krate.default.withDefault"), + ), +) public inline fun Krate.gsonPref( - key: String, + key: String? = null, defaultValue: T, ): PropertyDelegateProvider> { - return gsonPrefImpl(key, defaultValue, object : TypeToken() {}.type) -} - -@PublishedApi -internal fun Krate.gsonPrefImpl( - key: String, - defaultValue: T, - type: Type, -): PropertyDelegateProvider> { - return GsonDelegateWithDefaultFactory(key, defaultValue, type) + return gsonPref(key).withDefault(defaultValue) } diff --git a/krate-gson/src/main/kotlin/hu/autsoft/krate/gson/ValidatedFunctions.kt b/krate-gson/src/main/kotlin/hu/autsoft/krate/gson/ValidatedFunctions.kt deleted file mode 100644 index 4dd05b3..0000000 --- a/krate-gson/src/main/kotlin/hu/autsoft/krate/gson/ValidatedFunctions.kt +++ /dev/null @@ -1,72 +0,0 @@ -@file:[Suppress("unused") OptIn(InternalKrateApi::class)] - -package hu.autsoft.krate.gson - -import com.google.gson.reflect.TypeToken -import hu.autsoft.krate.Krate -import hu.autsoft.krate.gson.default.GsonDelegateWithDefaultFactory -import hu.autsoft.krate.gson.optional.GsonDelegateFactory -import hu.autsoft.krate.internal.InternalKrateApi -import hu.autsoft.krate.validation.ValidatedPreferenceDelegateFactory -import java.lang.reflect.Type -import kotlin.properties.PropertyDelegateProvider -import kotlin.properties.ReadWriteProperty - -/** - * Creates a validated, optional preference of type T with the given [key] in this [Krate] instance. - * This value will be serialized using Gson. - */ -@Deprecated( - message = "Use .validate {} on a gsonPref instead", - level = DeprecationLevel.ERROR, - replaceWith = ReplaceWith( - "this.gsonPref(key).validate(isValid)", - imports = arrayOf("hu.autsoft.krate.validation.validate"), - ), -) -public inline fun Krate.gsonPref( - key: String, - noinline isValid: (newValue: T?) -> Boolean, -): PropertyDelegateProvider> { - return gsonPrefImpl(key, object : TypeToken() {}.type, isValid) -} - -@PublishedApi -internal fun Krate.gsonPrefImpl( - key: String, - type: Type, - isValid: (newValue: T?) -> Boolean, -): PropertyDelegateProvider> { - return ValidatedPreferenceDelegateFactory(GsonDelegateFactory(key, type), isValid) -} - -/** - * Creates a validated, non-optional preference of type T with the given [key] and [defaultValue] - * in this [Krate] instance. - * This value will be serialized using Gson. - */ -@Deprecated( - message = "Use .validate {} on a gsonPref instead", - level = DeprecationLevel.ERROR, - replaceWith = ReplaceWith( - "this.gsonPref(key, defaultValue).validate(isValid)", - imports = arrayOf("hu.autsoft.krate.validation.validate"), - ), -) -public inline fun Krate.gsonPref( - key: String, - defaultValue: T, - noinline isValid: (newValue: T) -> Boolean, -): PropertyDelegateProvider> { - return gsonPrefImpl(key, defaultValue, object : TypeToken() {}.type, isValid) -} - -@PublishedApi -internal fun Krate.gsonPrefImpl( - key: String, - defaultValue: T, - type: Type, - isValid: (newValue: T) -> Boolean, -): PropertyDelegateProvider> { - return ValidatedPreferenceDelegateFactory(GsonDelegateWithDefaultFactory(key, defaultValue, type), isValid) -} diff --git a/krate-gson/src/main/kotlin/hu/autsoft/krate/gson/default/GsonDelegateWithDefault.kt b/krate-gson/src/main/kotlin/hu/autsoft/krate/gson/default/GsonDelegateWithDefault.kt deleted file mode 100644 index febc78d..0000000 --- a/krate-gson/src/main/kotlin/hu/autsoft/krate/gson/default/GsonDelegateWithDefault.kt +++ /dev/null @@ -1,44 +0,0 @@ -package hu.autsoft.krate.gson.default - -import com.google.gson.TypeAdapter -import com.google.gson.reflect.TypeToken -import hu.autsoft.krate.Krate -import hu.autsoft.krate.gson.internalGson -import hu.autsoft.krate.gson.util.edit -import java.lang.reflect.Type -import kotlin.properties.PropertyDelegateProvider -import kotlin.properties.ReadWriteProperty -import kotlin.reflect.KProperty - -private class GsonDelegateWithDefault( - private val key: String, - private val default: T, - private val adapter: TypeAdapter, -) : ReadWriteProperty { - override operator fun getValue(thisRef: Krate, property: KProperty<*>): T { - if (!thisRef.sharedPreferences.contains(key)) { - return default - } - - val string = thisRef.sharedPreferences.getString(key, null) - return adapter.fromJson(string) - } - - override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: T) { - thisRef.sharedPreferences.edit { - putString(key, adapter.toJson(value)) - } - } -} - -internal class GsonDelegateWithDefaultFactory( - private val key: String, - private val default: T, - private val type: Type, -) : PropertyDelegateProvider> { - override fun provideDelegate(thisRef: Krate, property: KProperty<*>): ReadWriteProperty { - @Suppress("UNCHECKED_CAST") - val adapter: TypeAdapter = thisRef.internalGson.getAdapter(TypeToken.get(type)) as TypeAdapter - return GsonDelegateWithDefault(key, default, adapter) - } -} diff --git a/krate-gson/src/main/kotlin/hu/autsoft/krate/gson/optional/GsonDelegate.kt b/krate-gson/src/main/kotlin/hu/autsoft/krate/gson/optional/GsonDelegate.kt index 01dde64..bcc2350 100644 --- a/krate-gson/src/main/kotlin/hu/autsoft/krate/gson/optional/GsonDelegate.kt +++ b/krate-gson/src/main/kotlin/hu/autsoft/krate/gson/optional/GsonDelegate.kt @@ -3,22 +3,23 @@ package hu.autsoft.krate.gson.optional import com.google.gson.TypeAdapter import com.google.gson.reflect.TypeToken import hu.autsoft.krate.Krate +import hu.autsoft.krate.base.KeyedKrateProperty +import hu.autsoft.krate.base.KeyedKratePropertyProvider import hu.autsoft.krate.gson.internalGson import hu.autsoft.krate.gson.util.edit import java.lang.reflect.Type -import kotlin.properties.PropertyDelegateProvider -import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty private class GsonDelegate( - private val key: String, + override val key: String, private val adapter: TypeAdapter, -) : ReadWriteProperty { +) : KeyedKrateProperty { + override operator fun getValue(thisRef: Krate, property: KProperty<*>): T? { - return if (!thisRef.sharedPreferences.contains(key)) { + return if (key !in thisRef.sharedPreferences) { null } else { - val string = thisRef.sharedPreferences.getString(key, null) + val string = requireNotNull(thisRef.sharedPreferences.getString(key, null)) adapter.fromJson(string) } } @@ -37,12 +38,13 @@ private class GsonDelegate( } internal class GsonDelegateFactory( - private val key: String, + private val key: String?, private val type: Type, -) : PropertyDelegateProvider> { - override fun provideDelegate(thisRef: Krate, property: KProperty<*>): ReadWriteProperty { +) : KeyedKratePropertyProvider { + + override fun provideDelegate(thisRef: Krate, property: KProperty<*>): KeyedKrateProperty { @Suppress("UNCHECKED_CAST") val adapter: TypeAdapter = thisRef.internalGson.getAdapter(TypeToken.get(type)) as TypeAdapter - return GsonDelegate(key, adapter) + return GsonDelegate(key ?: property.name, adapter) } } diff --git a/krate-gson/src/test/java/hu/autsoft/krate/gson/GsonTestKrate.kt b/krate-gson/src/test/java/hu/autsoft/krate/gson/GsonTestKrate.kt index ee46f64..b144022 100644 --- a/krate-gson/src/test/java/hu/autsoft/krate/gson/GsonTestKrate.kt +++ b/krate-gson/src/test/java/hu/autsoft/krate/gson/GsonTestKrate.kt @@ -2,6 +2,7 @@ package hu.autsoft.krate.gson import android.content.Context import hu.autsoft.krate.SimpleKrate +import hu.autsoft.krate.default.withDefault import hu.autsoft.krate.validation.validate @@ -20,22 +21,25 @@ internal class GsonTestKrate(context: Context) : SimpleKrate(context) { var listOfValues: List? by gsonPref("listOfValues") - var simpleValueWithDefault: TestModel - by gsonPref("simpleValueWithDefault", DEFAULT_SIMPLE_VALUE) + var simpleValueWithDefault + by gsonPref("simpleValueWithDefault") + .withDefault(DEFAULT_SIMPLE_VALUE) - var listOfValuesWithDefault: List - by gsonPref("listOfValuesWithDefault", defaultValue = DEFAULT_LIST_VALUE) + var listOfValuesWithDefault + by gsonPref>("listOfValuesWithDefault") + .withDefault(DEFAULT_LIST_VALUE) - var validatedValue: TestModel - by gsonPref(key = "validatedValue", defaultValue = DEFAULT_SIMPLE_VALUE) - .validate { newValue -> - newValue.x < newValue.y // arbitrary rule - } + var validatedValue + by gsonPref("validatedValue") + .withDefault(DEFAULT_SIMPLE_VALUE) + .validate { newValue -> + newValue.x < newValue.y // arbitrary rule + } var validatedOptionalValue: List? - by gsonPref>(key = "validatedOptionalValue") - .validate { newValue -> - newValue.isNullOrEmpty().not() - } + by gsonPref>(key = "validatedOptionalValue") + .validate { newValue -> + newValue.isNullOrEmpty().not() + } } diff --git a/krate-kotlinx/build.gradle b/krate-kotlinx/build.gradle index 51b2146..4b73b09 100644 --- a/krate-kotlinx/build.gradle +++ b/krate-kotlinx/build.gradle @@ -30,7 +30,7 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { dependencies { implementation project(':krate') - implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.2" + implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.1" testImplementation "junit:junit:$junit_version" testImplementation "org.robolectric:robolectric:$robolectric_version" diff --git a/krate-kotlinx/src/main/kotlin/hu/autsoft/krate/kotlinx/Functions.kt b/krate-kotlinx/src/main/kotlin/hu/autsoft/krate/kotlinx/Functions.kt index c128239..a106f87 100644 --- a/krate-kotlinx/src/main/kotlin/hu/autsoft/krate/kotlinx/Functions.kt +++ b/krate-kotlinx/src/main/kotlin/hu/autsoft/krate/kotlinx/Functions.kt @@ -3,7 +3,8 @@ package hu.autsoft.krate.kotlinx import hu.autsoft.krate.Krate -import hu.autsoft.krate.kotlinx.default.KotlinxDelegateWithDefaultFactory +import hu.autsoft.krate.base.KeyedKratePropertyProvider +import hu.autsoft.krate.default.withDefault import hu.autsoft.krate.kotlinx.optional.KotlinxDelegateFactory import kotlin.properties.PropertyDelegateProvider import kotlin.properties.ReadWriteProperty @@ -12,40 +13,43 @@ import kotlin.reflect.typeOf /** * Creates an optional preference of type T with the given [key] in this [Krate] instance. + * If no [key] is provided, the property's name will be used as the key. + * * This instance will be serialized using kotlinx.serialization. */ @OptIn(ExperimentalStdlibApi::class) public inline fun Krate.kotlinxPref( - key: String, -): PropertyDelegateProvider> { + key: String? = null, +): KeyedKratePropertyProvider { return kotlinxPrefImpl(key, typeOf()) } @PublishedApi internal fun Krate.kotlinxPrefImpl( - key: String, + key: String?, type: KType, -): PropertyDelegateProvider> { +): KeyedKratePropertyProvider { return KotlinxDelegateFactory(key, type) } /** * Creates a non-optional preference of type T with the given [key] and [defaultValue] in this [Krate] instance. + * If no [key] is provided, the property's name will be used as the key. + * * This instance will be serialized using kotlinx.serialization. */ +@Deprecated( + message = "Use .withDefault() on a kotlinxPref instead", + level = DeprecationLevel.ERROR, + replaceWith = ReplaceWith( + "this.kotlinxPref(key).withDefault(defaultValue)", + imports = arrayOf("hu.autsoft.krate.default.withDefault"), + ), +) @OptIn(ExperimentalStdlibApi::class) public inline fun Krate.kotlinxPref( - key: String, + key: String? = null, defaultValue: T, ): PropertyDelegateProvider> { - return kotlinxPrefImpl(key, defaultValue, typeOf()) -} - -@PublishedApi -internal fun Krate.kotlinxPrefImpl( - key: String, - defaultValue: T, - type: KType, -): PropertyDelegateProvider> { - return KotlinxDelegateWithDefaultFactory(key, defaultValue, type) + return kotlinxPref(key).withDefault(defaultValue) } diff --git a/krate-kotlinx/src/main/kotlin/hu/autsoft/krate/kotlinx/default/KotlinxDelegateWithDefault.kt b/krate-kotlinx/src/main/kotlin/hu/autsoft/krate/kotlinx/default/KotlinxDelegateWithDefault.kt deleted file mode 100644 index 0a467c1..0000000 --- a/krate-kotlinx/src/main/kotlin/hu/autsoft/krate/kotlinx/default/KotlinxDelegateWithDefault.kt +++ /dev/null @@ -1,43 +0,0 @@ -package hu.autsoft.krate.kotlinx.default - -import hu.autsoft.krate.Krate -import hu.autsoft.krate.kotlinx.internalJson -import hu.autsoft.krate.kotlinx.util.edit -import kotlinx.serialization.KSerializer -import kotlinx.serialization.serializer -import kotlin.properties.PropertyDelegateProvider -import kotlin.properties.ReadWriteProperty -import kotlin.reflect.KProperty -import kotlin.reflect.KType - -private class KotlinxDelegateWithDefault( - private val key: String, - private val default: T, - private val serializer: KSerializer, -) : ReadWriteProperty { - override operator fun getValue(thisRef: Krate, property: KProperty<*>): T { - if (!thisRef.sharedPreferences.contains(key)) { - return default - } - val string = requireNotNull(thisRef.sharedPreferences.getString(key, null)) - return thisRef.internalJson.decodeFromString(serializer, string) - } - - override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: T) { - thisRef.sharedPreferences.edit { - putString(key, thisRef.internalJson.encodeToString(serializer, value)) - } - } -} - -internal class KotlinxDelegateWithDefaultFactory( - private val key: String, - private val default: T, - private val type: KType, -) : PropertyDelegateProvider> { - override fun provideDelegate(thisRef: Krate, property: KProperty<*>): ReadWriteProperty { - @Suppress("UNCHECKED_CAST") - val serializer = thisRef.internalJson.serializersModule.serializer(type) as KSerializer - return KotlinxDelegateWithDefault(key, default, serializer) - } -} diff --git a/krate-kotlinx/src/main/kotlin/hu/autsoft/krate/kotlinx/optional/KotlinxDelegate.kt b/krate-kotlinx/src/main/kotlin/hu/autsoft/krate/kotlinx/optional/KotlinxDelegate.kt index bbf269f..6457ade 100644 --- a/krate-kotlinx/src/main/kotlin/hu/autsoft/krate/kotlinx/optional/KotlinxDelegate.kt +++ b/krate-kotlinx/src/main/kotlin/hu/autsoft/krate/kotlinx/optional/KotlinxDelegate.kt @@ -1,22 +1,23 @@ package hu.autsoft.krate.kotlinx.optional import hu.autsoft.krate.Krate +import hu.autsoft.krate.base.KeyedKrateProperty +import hu.autsoft.krate.base.KeyedKratePropertyProvider import hu.autsoft.krate.kotlinx.internalJson import hu.autsoft.krate.kotlinx.util.edit import kotlinx.serialization.KSerializer import kotlinx.serialization.serializer -import kotlin.properties.PropertyDelegateProvider -import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty import kotlin.reflect.KType private class KotlinxDelegate( - private val key: String, + override val key: String, private val serializer: KSerializer, -) : ReadWriteProperty { +) : KeyedKrateProperty { + override operator fun getValue(thisRef: Krate, property: KProperty<*>): T? { - return if (!thisRef.sharedPreferences.contains(key)) { + return if (key !in thisRef.sharedPreferences) { null } else { val string = requireNotNull(thisRef.sharedPreferences.getString(key, null)) @@ -38,12 +39,13 @@ private class KotlinxDelegate( } internal class KotlinxDelegateFactory( - private val key: String, + private val key: String?, private val type: KType, -) : PropertyDelegateProvider> { - override fun provideDelegate(thisRef: Krate, property: KProperty<*>): ReadWriteProperty { +) : KeyedKratePropertyProvider { + + override fun provideDelegate(thisRef: Krate, property: KProperty<*>): KeyedKrateProperty { @Suppress("UNCHECKED_CAST") val serializer = thisRef.internalJson.serializersModule.serializer(type) as KSerializer - return KotlinxDelegate(key, serializer) + return KotlinxDelegate(key ?: property.name, serializer) } } diff --git a/krate-kotlinx/src/test/java/hu/autsoft/krate/kotlinx/KotlinxTestKrate.kt b/krate-kotlinx/src/test/java/hu/autsoft/krate/kotlinx/KotlinxTestKrate.kt index 315797b..1c65451 100644 --- a/krate-kotlinx/src/test/java/hu/autsoft/krate/kotlinx/KotlinxTestKrate.kt +++ b/krate-kotlinx/src/test/java/hu/autsoft/krate/kotlinx/KotlinxTestKrate.kt @@ -2,6 +2,7 @@ package hu.autsoft.krate.kotlinx import android.content.Context import hu.autsoft.krate.SimpleKrate +import hu.autsoft.krate.default.withDefault import hu.autsoft.krate.validation.validate @@ -20,22 +21,24 @@ internal class KotlinxTestKrate(context: Context) : SimpleKrate(context) { var listOfValues: List? by kotlinxPref("listOfValues") - var simpleValueWithDefault: TestModel - by kotlinxPref("simpleValueWithDefault", DEFAULT_SIMPLE_VALUE) - - var listOfValuesWithDefault: List - by kotlinxPref("listOfValuesWithDefault", defaultValue = DEFAULT_LIST_VALUE) - - var validatedValue: TestModel - by kotlinxPref(key = "validatedValue", defaultValue = DEFAULT_SIMPLE_VALUE) - .validate { newValue -> - newValue.x < newValue.y // arbitrary rule - } - - var validatedOptionalValue: List? - by kotlinxPref>(key = "validatedOptionalValue") - .validate { newValue -> - newValue.isNullOrEmpty().not() // arbitrary rule - } + var simpleValueWithDefault + by kotlinxPref("simpleValueWithDefault").withDefault(DEFAULT_SIMPLE_VALUE) + + var listOfValuesWithDefault + by kotlinxPref>("listOfValuesWithDefault") + .withDefault(DEFAULT_LIST_VALUE) + + var validatedValue + by kotlinxPref(key = "validatedValue") + .withDefault(DEFAULT_SIMPLE_VALUE) + .validate { newValue -> + newValue.x < newValue.y // arbitrary rule + } + + var validatedOptionalValue + by kotlinxPref>(key = "validatedOptionalValue") + .validate { newValue -> + newValue.isNullOrEmpty().not() // arbitrary rule + } } diff --git a/krate-moshi-codegen/src/test/java/hu/autsoft/krate/moshi/MoshiTestKrate.kt b/krate-moshi-codegen/src/test/java/hu/autsoft/krate/moshi/MoshiTestKrate.kt index 800ff45..58c4a28 100644 --- a/krate-moshi-codegen/src/test/java/hu/autsoft/krate/moshi/MoshiTestKrate.kt +++ b/krate-moshi-codegen/src/test/java/hu/autsoft/krate/moshi/MoshiTestKrate.kt @@ -2,6 +2,7 @@ package hu.autsoft.krate.moshi import android.content.Context import hu.autsoft.krate.SimpleKrate +import hu.autsoft.krate.default.withDefault import hu.autsoft.krate.validation.validate @@ -20,22 +21,25 @@ internal class MoshiTestKrate(context: Context) : SimpleKrate(context) { var listOfValues: List? by moshiPref("listOfValues") - var simpleValueWithDefault: TestModel - by moshiPref("simpleValueWithDefault", DEFAULT_SIMPLE_VALUE) - - var listOfValuesWithDefault: List - by moshiPref("listOfValuesWithDefault", defaultValue = DEFAULT_LIST_VALUE) - - var validatedValue: TestModel - by moshiPref(key = "validatedValue", defaultValue = DEFAULT_SIMPLE_VALUE) - .validate { newValue -> - newValue.x < newValue.y // arbitrary rule - } - - var validatedOptionalValue: List? - by moshiPref>(key = "validatedOptionalValue") - .validate { newValue -> - newValue.isNullOrEmpty().not() // arbitrary rule - } + var simpleValueWithDefault + by moshiPref("simpleValueWithDefault") + .withDefault(DEFAULT_SIMPLE_VALUE) + + var listOfValuesWithDefault + by moshiPref>("listOfValuesWithDefault") + .withDefault(DEFAULT_LIST_VALUE) + + var validatedValue + by moshiPref("validatedValue") + .withDefault(DEFAULT_SIMPLE_VALUE) + .validate { newValue -> + newValue.x < newValue.y // arbitrary rule + } + + var validatedOptionalValue + by moshiPref>(key = "validatedOptionalValue") + .validate { newValue -> + newValue.isNullOrEmpty().not() // arbitrary rule + } } diff --git a/krate-moshi-core/src/main/kotlin/hu/autsoft/krate/moshi/Functions.kt b/krate-moshi-core/src/main/kotlin/hu/autsoft/krate/moshi/Functions.kt index fc18755..2757d3f 100644 --- a/krate-moshi-core/src/main/kotlin/hu/autsoft/krate/moshi/Functions.kt +++ b/krate-moshi-core/src/main/kotlin/hu/autsoft/krate/moshi/Functions.kt @@ -3,7 +3,8 @@ package hu.autsoft.krate.moshi import hu.autsoft.krate.Krate -import hu.autsoft.krate.moshi.default.MoshiDelegateWithDefaultFactory +import hu.autsoft.krate.base.KeyedKratePropertyProvider +import hu.autsoft.krate.default.withDefault import hu.autsoft.krate.moshi.optional.MoshiDelegateFactory import java.lang.reflect.Type import kotlin.properties.PropertyDelegateProvider @@ -13,40 +14,43 @@ import kotlin.reflect.typeOf /** * Creates an optional preference of type T with the given [key] in this [Krate] instance. + * If no [key] is provided, the property's name will be used as the key. + * * This instance will be serialized using Moshi. */ @OptIn(ExperimentalStdlibApi::class) public inline fun Krate.moshiPref( - key: String, -): PropertyDelegateProvider> { + key: String? = null, +): KeyedKratePropertyProvider { return moshiPrefImpl(key, typeOf().javaType) } @PublishedApi internal fun Krate.moshiPrefImpl( - key: String, + key: String?, type: Type, -): PropertyDelegateProvider> { +): KeyedKratePropertyProvider { return MoshiDelegateFactory(key, type) } /** * Creates a non-optional preference of type T with the given [key] and [defaultValue] in this [Krate] instance. + * If no [key] is provided, the property's name will be used as the key. + * * This instance will be serialized using Moshi. */ +@Deprecated( + message = "Use .withDefault() on a moshiPref instead", + level = DeprecationLevel.ERROR, + replaceWith = ReplaceWith( + "this.moshiPref(key).withDefault(defaultValue)", + imports = arrayOf("hu.autsoft.krate.default.withDefault"), + ), +) @OptIn(ExperimentalStdlibApi::class) public inline fun Krate.moshiPref( - key: String, + key: String? = null, defaultValue: T, ): PropertyDelegateProvider> { - return moshiPrefImpl(key, defaultValue, typeOf().javaType) -} - -@PublishedApi -internal fun Krate.moshiPrefImpl( - key: String, - defaultValue: T, - type: Type, -): PropertyDelegateProvider> { - return MoshiDelegateWithDefaultFactory(key, defaultValue, type) + return moshiPref(key).withDefault(defaultValue) } diff --git a/krate-moshi-core/src/main/kotlin/hu/autsoft/krate/moshi/MoshiInstances.kt b/krate-moshi-core/src/main/kotlin/hu/autsoft/krate/moshi/MoshiInstances.kt index 10116da..d278d0c 100644 --- a/krate-moshi-core/src/main/kotlin/hu/autsoft/krate/moshi/MoshiInstances.kt +++ b/krate-moshi-core/src/main/kotlin/hu/autsoft/krate/moshi/MoshiInstances.kt @@ -9,8 +9,8 @@ internal val moshiInstances: MutableMap = mutableMapOf(key).validate(isValid)", - imports = arrayOf("hu.autsoft.krate.validation.validate"), - ), -) -public inline fun Krate.moshiPref( - key: String, - noinline isValid: (newValue: T?) -> Boolean, -): PropertyDelegateProvider> { - return moshiPrefImpl(key, typeOf().javaType, isValid) -} - -@PublishedApi -internal fun Krate.moshiPrefImpl( - key: String, - type: Type, - isValid: (newValue: T?) -> Boolean, -): PropertyDelegateProvider> { - return ValidatedPreferenceDelegateFactory(MoshiDelegateFactory(key, type), isValid) -} - -/** - * Creates a validated, non-optional preference of type T with the given [key] and [defaultValue] - * in this [Krate] instance. - * This value will be serialized using Moshi. - */ -@OptIn(ExperimentalStdlibApi::class) -@Deprecated( - message = "Use .validate {} on a moshiPref instead", - level = DeprecationLevel.ERROR, - replaceWith = ReplaceWith( - "this.moshiPref(key, defaultValue).validate(isValid)", - imports = arrayOf("hu.autsoft.krate.validation.validate"), - ), -) -public inline fun Krate.moshiPref( - key: String, - defaultValue: T, - noinline isValid: (newValue: T) -> Boolean, -): PropertyDelegateProvider> { - return moshiPrefImpl(key, defaultValue, typeOf().javaType, isValid) -} - -@PublishedApi -internal fun Krate.moshiPrefImpl( - key: String, - defaultValue: T, - type: Type, - isValid: (newValue: T) -> Boolean, -): PropertyDelegateProvider> { - return ValidatedPreferenceDelegateFactory(MoshiDelegateWithDefaultFactory(key, defaultValue, type), isValid) -} diff --git a/krate-moshi-core/src/main/kotlin/hu/autsoft/krate/moshi/default/MoshiDelegateWithDefault.kt b/krate-moshi-core/src/main/kotlin/hu/autsoft/krate/moshi/default/MoshiDelegateWithDefault.kt deleted file mode 100644 index 7ebbd06..0000000 --- a/krate-moshi-core/src/main/kotlin/hu/autsoft/krate/moshi/default/MoshiDelegateWithDefault.kt +++ /dev/null @@ -1,42 +0,0 @@ -package hu.autsoft.krate.moshi.default - -import com.squareup.moshi.JsonAdapter -import hu.autsoft.krate.Krate -import hu.autsoft.krate.moshi.realMoshiInstance -import hu.autsoft.krate.moshi.util.edit -import java.lang.reflect.Type -import kotlin.properties.PropertyDelegateProvider -import kotlin.properties.ReadWriteProperty -import kotlin.reflect.KProperty - - -private class MoshiDelegateWithDefault( - private val key: String, - private val default: T, - private val adapter: JsonAdapter, -) : ReadWriteProperty { - override operator fun getValue(thisRef: Krate, property: KProperty<*>): T { - if (!thisRef.sharedPreferences.contains(key)) { - return default - } - val string = requireNotNull(thisRef.sharedPreferences.getString(key, null)) - return requireNotNull(adapter.fromJson(string)) - } - - override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: T) { - thisRef.sharedPreferences.edit { - putString(key, adapter.toJson(value)) - } - } -} - -internal class MoshiDelegateWithDefaultFactory( - private val key: String, - private val default: T, - private val type: Type, -) : PropertyDelegateProvider> { - override fun provideDelegate(thisRef: Krate, property: KProperty<*>): ReadWriteProperty { - val adapter = thisRef.realMoshiInstance.adapter(type) - return MoshiDelegateWithDefault(key, default, adapter) - } -} diff --git a/krate-moshi-core/src/main/kotlin/hu/autsoft/krate/moshi/optional/MoshiDelegate.kt b/krate-moshi-core/src/main/kotlin/hu/autsoft/krate/moshi/optional/MoshiDelegate.kt index ee70320..ef03534 100644 --- a/krate-moshi-core/src/main/kotlin/hu/autsoft/krate/moshi/optional/MoshiDelegate.kt +++ b/krate-moshi-core/src/main/kotlin/hu/autsoft/krate/moshi/optional/MoshiDelegate.kt @@ -2,19 +2,20 @@ package hu.autsoft.krate.moshi.optional import com.squareup.moshi.JsonAdapter import hu.autsoft.krate.Krate +import hu.autsoft.krate.base.KeyedKrateProperty +import hu.autsoft.krate.base.KeyedKratePropertyProvider import hu.autsoft.krate.moshi.realMoshiInstance import hu.autsoft.krate.moshi.util.edit import java.lang.reflect.Type -import kotlin.properties.PropertyDelegateProvider -import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty private class MoshiDelegate( - private val key: String, + override val key: String, private val adapter: JsonAdapter, -) : ReadWriteProperty { +) : KeyedKrateProperty { + override fun getValue(thisRef: Krate, property: KProperty<*>): T? { - return if (!thisRef.sharedPreferences.contains(key)) { + return if (key !in thisRef.sharedPreferences) { null } else { val string = requireNotNull(thisRef.sharedPreferences.getString(key, null)) @@ -36,11 +37,12 @@ private class MoshiDelegate( } internal class MoshiDelegateFactory( - private val key: String, + private val key: String?, private val type: Type, -) : PropertyDelegateProvider> { - override fun provideDelegate(thisRef: Krate, property: KProperty<*>): ReadWriteProperty { +) : KeyedKratePropertyProvider { + + override fun provideDelegate(thisRef: Krate, property: KProperty<*>): KeyedKrateProperty { val adapter = thisRef.realMoshiInstance.adapter(type) - return MoshiDelegate(key, adapter) + return MoshiDelegate(key ?: property.name, adapter) } } diff --git a/krate-moshi-reflect/src/test/java/hu/autsoft/krate/moshi/MoshiTestKrate.kt b/krate-moshi-reflect/src/test/java/hu/autsoft/krate/moshi/MoshiTestKrate.kt index 800ff45..58c4a28 100644 --- a/krate-moshi-reflect/src/test/java/hu/autsoft/krate/moshi/MoshiTestKrate.kt +++ b/krate-moshi-reflect/src/test/java/hu/autsoft/krate/moshi/MoshiTestKrate.kt @@ -2,6 +2,7 @@ package hu.autsoft.krate.moshi import android.content.Context import hu.autsoft.krate.SimpleKrate +import hu.autsoft.krate.default.withDefault import hu.autsoft.krate.validation.validate @@ -20,22 +21,25 @@ internal class MoshiTestKrate(context: Context) : SimpleKrate(context) { var listOfValues: List? by moshiPref("listOfValues") - var simpleValueWithDefault: TestModel - by moshiPref("simpleValueWithDefault", DEFAULT_SIMPLE_VALUE) - - var listOfValuesWithDefault: List - by moshiPref("listOfValuesWithDefault", defaultValue = DEFAULT_LIST_VALUE) - - var validatedValue: TestModel - by moshiPref(key = "validatedValue", defaultValue = DEFAULT_SIMPLE_VALUE) - .validate { newValue -> - newValue.x < newValue.y // arbitrary rule - } - - var validatedOptionalValue: List? - by moshiPref>(key = "validatedOptionalValue") - .validate { newValue -> - newValue.isNullOrEmpty().not() // arbitrary rule - } + var simpleValueWithDefault + by moshiPref("simpleValueWithDefault") + .withDefault(DEFAULT_SIMPLE_VALUE) + + var listOfValuesWithDefault + by moshiPref>("listOfValuesWithDefault") + .withDefault(DEFAULT_LIST_VALUE) + + var validatedValue + by moshiPref("validatedValue") + .withDefault(DEFAULT_SIMPLE_VALUE) + .validate { newValue -> + newValue.x < newValue.y // arbitrary rule + } + + var validatedOptionalValue + by moshiPref>(key = "validatedOptionalValue") + .validate { newValue -> + newValue.isNullOrEmpty().not() // arbitrary rule + } } diff --git a/krate/src/main/kotlin/hu/autsoft/krate/Functions.kt b/krate/src/main/kotlin/hu/autsoft/krate/Functions.kt index 8f3f2c9..18449c1 100644 --- a/krate/src/main/kotlin/hu/autsoft/krate/Functions.kt +++ b/krate/src/main/kotlin/hu/autsoft/krate/Functions.kt @@ -2,101 +2,158 @@ package hu.autsoft.krate -import hu.autsoft.krate.default.BooleanDelegateWithDefault -import hu.autsoft.krate.default.FloatDelegateWithDefault -import hu.autsoft.krate.default.IntDelegateWithDefault -import hu.autsoft.krate.default.LongDelegateWithDefault -import hu.autsoft.krate.default.StringDelegateWithDefault -import hu.autsoft.krate.default.StringSetDelegateWithDefault -import hu.autsoft.krate.optional.BooleanDelegate -import hu.autsoft.krate.optional.FloatDelegate -import hu.autsoft.krate.optional.IntDelegate -import hu.autsoft.krate.optional.LongDelegate -import hu.autsoft.krate.optional.StringDelegate -import hu.autsoft.krate.optional.StringSetDelegate +import hu.autsoft.krate.base.KeyedKratePropertyProvider +import hu.autsoft.krate.default.withDefault +import hu.autsoft.krate.optional.BooleanDelegateProvider +import hu.autsoft.krate.optional.FloatDelegateProvider +import hu.autsoft.krate.optional.IntDelegateProvider +import hu.autsoft.krate.optional.LongDelegateProvider +import hu.autsoft.krate.optional.StringDelegateProvider +import hu.autsoft.krate.optional.StringSetDelegateProvider +import kotlin.properties.PropertyDelegateProvider import kotlin.properties.ReadWriteProperty /** * Creates an optional preference of type [Boolean] with the given [key] in this [Krate] instance. + * If no [key] is provided, the property's name will be used as the key. */ -public fun Krate.booleanPref(key: String): ReadWriteProperty { - return BooleanDelegate(key) +public fun Krate.booleanPref(key: String? = null): KeyedKratePropertyProvider { + return BooleanDelegateProvider(key) } /** * Creates an optional preference of type [Float] with the given [key] in this [Krate] instance. + * If no [key] is provided, the property's name will be used as the key. */ -public fun Krate.floatPref(key: String): ReadWriteProperty { - return FloatDelegate(key) +public fun Krate.floatPref(key: String? = null): KeyedKratePropertyProvider { + return FloatDelegateProvider(key) } /** * Creates an optional preference of type [Int] with the given [key] in this [Krate] instance. + * If no [key] is provided, the property's name will be used as the key. */ -public fun Krate.intPref(key: String): ReadWriteProperty { - return IntDelegate(key) +public fun Krate.intPref(key: String? = null): KeyedKratePropertyProvider { + return IntDelegateProvider(key) } /** * Creates an optional preference of type [Long] with the given [key] in this [Krate] instance. + * If no [key] is provided, the property's name will be used as the key. */ -public fun Krate.longPref(key: String): ReadWriteProperty { - return LongDelegate(key) +public fun Krate.longPref(key: String? = null): KeyedKratePropertyProvider { + return LongDelegateProvider(key) } /** * Creates an optional preference of type [String] with the given [key] in this [Krate] instance. + * If no [key] is provided, the property's name will be used as the key. */ -public fun Krate.stringPref(key: String): ReadWriteProperty { - return StringDelegate(key) +public fun Krate.stringPref(key: String? = null): KeyedKratePropertyProvider { + return StringDelegateProvider(key) } /** * Creates an optional preference of type Set with the given [key] in this [Krate] instance. + * If no [key] is provided, the property's name will be used as the key. */ -public fun Krate.stringSetPref(key: String): ReadWriteProperty?> { - return StringSetDelegate(key) +public fun Krate.stringSetPref(key: String? = null): KeyedKratePropertyProvider?> { + return StringSetDelegateProvider(key) } /** * Creates a non-optional preference of type [Boolean] with the given [key] and [defaultValue] in this [Krate] instance. + * If no [key] is provided, the property's name will be used as the key. */ -public fun Krate.booleanPref(key: String, defaultValue: Boolean): ReadWriteProperty { - return BooleanDelegateWithDefault(key, defaultValue) +@Deprecated( + message = "Use .withDefault() on a booleanPref instead", + level = DeprecationLevel.ERROR, + replaceWith = ReplaceWith( + "this.booleanPref(key).withDefault(defaultValue)", + imports = arrayOf("hu.autsoft.krate.default.withDefault"), + ), +) +public fun Krate.booleanPref(key: String? = null, defaultValue: Boolean): PropertyDelegateProvider> { + return booleanPref(key).withDefault(defaultValue) } /** * Creates a non-optional preference of type [Float] with the given [key] and [defaultValue] in this [Krate] instance. + * If no [key] is provided, the property's name will be used as the key. */ -public fun Krate.floatPref(key: String, defaultValue: Float): ReadWriteProperty { - return FloatDelegateWithDefault(key, defaultValue) +@Deprecated( + message = "Use .withDefault() on a floatPref instead", + level = DeprecationLevel.ERROR, + replaceWith = ReplaceWith( + "this.floatPref(key).withDefault(defaultValue)", + imports = arrayOf("hu.autsoft.krate.default.withDefault"), + ), +) +public fun Krate.floatPref(key: String? = null, defaultValue: Float): PropertyDelegateProvider> { + return floatPref(key).withDefault(defaultValue) } /** * Creates a non-optional preference of type [Int] with the given [key] and [defaultValue] in this [Krate] instance. + * If no [key] is provided, the property's name will be used as the key. */ -public fun Krate.intPref(key: String, defaultValue: Int): ReadWriteProperty { - return IntDelegateWithDefault(key, defaultValue) +@Deprecated( + message = "Use .withDefault() on an intPref instead", + level = DeprecationLevel.ERROR, + replaceWith = ReplaceWith( + "this.intPref(key).withDefault(defaultValue)", + imports = arrayOf("hu.autsoft.krate.default.withDefault"), + ), +) +public fun Krate.intPref(key: String? = null, defaultValue: Int): PropertyDelegateProvider> { + return intPref(key).withDefault(defaultValue) } /** * Creates a non-optional preference of type [Long] with the given [key] and [defaultValue] in this [Krate] instance. + * If no [key] is provided, the property's name will be used as the key. */ -public fun Krate.longPref(key: String, defaultValue: Long): ReadWriteProperty { - return LongDelegateWithDefault(key, defaultValue) +@Deprecated( + message = "Use .withDefault() on a longPref instead", + level = DeprecationLevel.ERROR, + replaceWith = ReplaceWith( + "this.longPref(key).withDefault(defaultValue)", + imports = arrayOf("hu.autsoft.krate.default.withDefault"), + ), +) +public fun Krate.longPref(key: String? = null, defaultValue: Long): PropertyDelegateProvider> { + return longPref(key).withDefault(defaultValue) } /** * Creates a non-optional preference of type [String] with the given [key] and [defaultValue] in this [Krate] instance. + * If no [key] is provided, the property's name will be used as the key. */ -public fun Krate.stringPref(key: String, defaultValue: String): ReadWriteProperty { - return StringDelegateWithDefault(key, defaultValue) +@Deprecated( + message = "Use .withDefault() on a stringPref instead", + level = DeprecationLevel.ERROR, + replaceWith = ReplaceWith( + "this.stringPref(key).withDefault(defaultValue)", + imports = arrayOf("hu.autsoft.krate.default.withDefault"), + ), +) +public fun Krate.stringPref(key: String? = null, defaultValue: String): PropertyDelegateProvider> { + return stringPref(key).withDefault(defaultValue) } /** * Creates a non-optional preference of type Set with the given [key] and [defaultValue] in this [Krate] instance. + * If no [key] is provided, the property's name will be used as the key. */ -public fun Krate.stringSetPref(key: String, defaultValue: Set): ReadWriteProperty> { - return StringSetDelegateWithDefault(key, defaultValue) +@Deprecated( + message = "Use .withDefault() on a stringSetPref instead", + level = DeprecationLevel.ERROR, + replaceWith = ReplaceWith( + "this.stringSetPref(key).withDefault(defaultValue)", + imports = arrayOf("hu.autsoft.krate.default.withDefault"), + ), +) +public fun Krate.stringSetPref(key: String? = null, defaultValue: Set): PropertyDelegateProvider>> { + return stringSetPref(key).withDefault(defaultValue) } diff --git a/krate/src/main/kotlin/hu/autsoft/krate/ValidatedFunctions.kt b/krate/src/main/kotlin/hu/autsoft/krate/ValidatedFunctions.kt deleted file mode 100644 index 1e1d27c..0000000 --- a/krate/src/main/kotlin/hu/autsoft/krate/ValidatedFunctions.kt +++ /dev/null @@ -1,232 +0,0 @@ -@file:[Suppress("unused") OptIn(InternalKrateApi::class)] - -package hu.autsoft.krate - -import hu.autsoft.krate.default.FloatDelegateWithDefault -import hu.autsoft.krate.default.IntDelegateWithDefault -import hu.autsoft.krate.default.LongDelegateWithDefault -import hu.autsoft.krate.default.StringDelegateWithDefault -import hu.autsoft.krate.default.StringSetDelegateWithDefault -import hu.autsoft.krate.internal.InternalKrateApi -import hu.autsoft.krate.optional.FloatDelegate -import hu.autsoft.krate.optional.IntDelegate -import hu.autsoft.krate.optional.LongDelegate -import hu.autsoft.krate.optional.StringDelegate -import hu.autsoft.krate.optional.StringSetDelegate -import hu.autsoft.krate.validation.ValidatedPreferenceDelegate -import kotlin.properties.ReadWriteProperty - -/** - * Creates a validated, optional preference of type [Float] with the given [key]. - * - * If a value being set to this preference returns `false` when checked by [isValid], - * an [IllegalArgumentException] will be thrown. - */ -@Deprecated( - message = "Use .validate {} on a floatPref instead", - level = DeprecationLevel.ERROR, - replaceWith = ReplaceWith( - "this.floatPref(key).validate(isValid)", - imports = arrayOf("hu.autsoft.krate.validation.validate"), - ), -) -public fun Krate.floatPref( - key: String, - isValid: (newValue: Float?) -> Boolean, -): ReadWriteProperty { - return ValidatedPreferenceDelegate(FloatDelegate(key), isValid) -} - -/** - * Creates a validated, non-optional preference of type [Float] with the given [key]. - * - * If a value being set to this preference returns `false` when checked by [isValid], - * an [IllegalArgumentException] will be thrown. - */ -@Deprecated( - message = "Use .validate {} on a floatPref instead", - level = DeprecationLevel.ERROR, - replaceWith = ReplaceWith( - "this.floatPref(key, defaultValue).validate(isValid)", - imports = arrayOf("hu.autsoft.krate.validation.validate"), - ), -) -public fun Krate.floatPref( - key: String, - defaultValue: Float, - isValid: (newValue: Float) -> Boolean, -): ReadWriteProperty { - return ValidatedPreferenceDelegate(FloatDelegateWithDefault(key, defaultValue), isValid) -} - -/** - * Creates a validated, optional preference of type [Int] with the given [key]. - * - * If a value being set to this preference returns `false` when checked by [isValid], - * an [IllegalArgumentException] will be thrown. - */ -@Deprecated( - message = "Use .validate {} on an intPref instead", - level = DeprecationLevel.ERROR, - replaceWith = ReplaceWith( - "this.intPref(key).validate(isValid)", - imports = arrayOf("hu.autsoft.krate.validation.validate"), - ), -) -public fun Krate.intPref( - key: String, - isValid: (newValue: Int?) -> Boolean, -): ReadWriteProperty { - return ValidatedPreferenceDelegate(IntDelegate(key), isValid) -} - -/** - * Creates a validated, non-optional preference of type [Int] with the given [key]. - * - * If a value being set to this preference returns `false` when checked by [isValid], - * an [IllegalArgumentException] will be thrown. - */ -@Deprecated( - message = "Use .validate {} on an intPref instead", - level = DeprecationLevel.ERROR, - replaceWith = ReplaceWith( - "this.intPref(key, defaultValue).validate(isValid)", - imports = arrayOf("hu.autsoft.krate.validation.validate"), - ), -) -public fun Krate.intPref( - key: String, - defaultValue: Int, - isValid: (newValue: Int) -> Boolean, -): ReadWriteProperty { - return ValidatedPreferenceDelegate(IntDelegateWithDefault(key, defaultValue), isValid) -} - -/** - * Creates a validated, optional preference of type [Long] with the given [key]. - * - * If a value being set to this preference returns `false` when checked by [isValid], - * an [IllegalArgumentException] will be thrown. - */ -@Deprecated( - message = "Use .validate {} on a longPref instead", - level = DeprecationLevel.ERROR, - replaceWith = ReplaceWith( - "this.longPref(key).validate(isValid)", - imports = arrayOf("hu.autsoft.krate.validation.validate"), - ), -) -public fun Krate.longPref( - key: String, - isValid: (newValue: Long?) -> Boolean, -): ReadWriteProperty { - return ValidatedPreferenceDelegate(LongDelegate(key), isValid) -} - -/** - * Creates a validated, non-optional preference of type [Long] with the given [key]. - * - * If a value being set to this preference returns `false` when checked by [isValid], - * an [IllegalArgumentException] will be thrown. - */ -@Deprecated( - message = "Use .validate {} on a longPref instead", - level = DeprecationLevel.ERROR, - replaceWith = ReplaceWith( - "this.longPref(key, defaultValue).validate(isValid)", - imports = arrayOf("hu.autsoft.krate.validation.validate"), - ), -) -public fun Krate.longPref( - key: String, - defaultValue: Long, - isValid: (newValue: Long) -> Boolean, -): ReadWriteProperty { - return ValidatedPreferenceDelegate(LongDelegateWithDefault(key, defaultValue), isValid) -} - -/** - * Creates a validated, optional preference of type [String] with the given [key]. - * - * If a value being set to this preference returns `false` when checked by [isValid], - * an [IllegalArgumentException] will be thrown. - */ -@Deprecated( - message = "Use .validate {} on a stringPref instead", - level = DeprecationLevel.ERROR, - replaceWith = ReplaceWith( - "this.stringPref(key).validate(isValid)", - imports = arrayOf("hu.autsoft.krate.validation.validate"), - ), -) -public fun Krate.stringPref( - key: String, - isValid: (newValue: String?) -> Boolean, -): ReadWriteProperty { - return ValidatedPreferenceDelegate(StringDelegate(key), isValid) -} - -/** - * Creates a validated, non-optional preference of type [String] with the given [key]. - * - * If a value being set to this preference returns `false` when checked by [isValid], - * an [IllegalArgumentException] will be thrown. - */ -@Deprecated( - message = "Use .validate {} on a stringPref instead", - level = DeprecationLevel.ERROR, - replaceWith = ReplaceWith( - "this.stringPref(key, defaultValue).validate(isValid)", - imports = arrayOf("hu.autsoft.krate.validation.validate"), - ), -) -public fun Krate.stringPref( - key: String, - defaultValue: String, - isValid: (newValue: String) -> Boolean, -): ReadWriteProperty { - return ValidatedPreferenceDelegate(StringDelegateWithDefault(key, defaultValue), isValid) -} - -/** - * Creates a validated, optional preference of type Set with the given [key]. - * - * If a value being set to this preference returns `false` when checked by [isValid], - * an [IllegalArgumentException] will be thrown. - */ -@Deprecated( - message = "Use .validate {} on a stringSetPref instead", - level = DeprecationLevel.ERROR, - replaceWith = ReplaceWith( - "this.stringSetPref(key).validate(isValid)", - imports = arrayOf("hu.autsoft.krate.validation.validate"), - ), -) -public fun Krate.stringSetPref( - key: String, - isValid: (newValue: Set?) -> Boolean, -): ReadWriteProperty?> { - return ValidatedPreferenceDelegate(StringSetDelegate(key), isValid) -} - -/** - * Creates a validated, non-optional preference of type Set with the given [key]. - * - * If a value being set to this preference returns `false` when checked by [isValid], - * an [IllegalArgumentException] will be thrown. - */ -@Deprecated( - message = "Use .validate {} on a stringSetPref instead", - level = DeprecationLevel.ERROR, - replaceWith = ReplaceWith( - "this.stringSetPref(key, defaultValue).validate(isValid)", - imports = arrayOf("hu.autsoft.krate.validation.validate"), - ), -) -public fun Krate.stringSetPref( - key: String, - defaultValue: Set, - isValid: (newValue: Set) -> Boolean, -): ReadWriteProperty> { - return ValidatedPreferenceDelegate(StringSetDelegateWithDefault(key, defaultValue), isValid) -} diff --git a/krate/src/main/kotlin/hu/autsoft/krate/base/KeyedKrateProperty.kt b/krate/src/main/kotlin/hu/autsoft/krate/base/KeyedKrateProperty.kt new file mode 100644 index 0000000..1ce2523 --- /dev/null +++ b/krate/src/main/kotlin/hu/autsoft/krate/base/KeyedKrateProperty.kt @@ -0,0 +1,12 @@ +package hu.autsoft.krate.base + +import hu.autsoft.krate.Krate +import kotlin.properties.ReadWriteProperty + +/** + * A Krate property delegate that holds the [key] to be used + * for storing the value. + */ +public interface KeyedKrateProperty : ReadWriteProperty { + public val key: String +} diff --git a/krate/src/main/kotlin/hu/autsoft/krate/base/KeyedKratePropertyProvider.kt b/krate/src/main/kotlin/hu/autsoft/krate/base/KeyedKratePropertyProvider.kt new file mode 100644 index 0000000..f546e07 --- /dev/null +++ b/krate/src/main/kotlin/hu/autsoft/krate/base/KeyedKratePropertyProvider.kt @@ -0,0 +1,9 @@ +package hu.autsoft.krate.base + +import hu.autsoft.krate.Krate +import kotlin.properties.PropertyDelegateProvider + +/** + * A [PropertyDelegateProvider] that always provides [KeyedKrateProperty] instances. + */ +public interface KeyedKratePropertyProvider : PropertyDelegateProvider> diff --git a/krate/src/main/kotlin/hu/autsoft/krate/default/BooleanDelegateWithDefault.kt b/krate/src/main/kotlin/hu/autsoft/krate/default/BooleanDelegateWithDefault.kt deleted file mode 100644 index 2586b44..0000000 --- a/krate/src/main/kotlin/hu/autsoft/krate/default/BooleanDelegateWithDefault.kt +++ /dev/null @@ -1,21 +0,0 @@ -package hu.autsoft.krate.default - -import hu.autsoft.krate.Krate -import hu.autsoft.krate.util.edit -import kotlin.properties.ReadWriteProperty -import kotlin.reflect.KProperty - -internal class BooleanDelegateWithDefault( - private val key: String, - private val default: Boolean, -) : ReadWriteProperty { - - override operator fun getValue(thisRef: Krate, property: KProperty<*>): Boolean { - return thisRef.sharedPreferences.getBoolean(key, default) - } - - override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: Boolean) { - thisRef.sharedPreferences.edit { putBoolean(key, value) } - } - -} diff --git a/krate/src/main/kotlin/hu/autsoft/krate/default/DelegateWithDefault.kt b/krate/src/main/kotlin/hu/autsoft/krate/default/DelegateWithDefault.kt new file mode 100644 index 0000000..bdc26f8 --- /dev/null +++ b/krate/src/main/kotlin/hu/autsoft/krate/default/DelegateWithDefault.kt @@ -0,0 +1,85 @@ +@file:Suppress("unused") + +package hu.autsoft.krate.default + +import hu.autsoft.krate.Krate +import hu.autsoft.krate.base.KeyedKrateProperty +import hu.autsoft.krate.base.KeyedKratePropertyProvider +import hu.autsoft.krate.internal.InternalKrateApi +import kotlin.properties.PropertyDelegateProvider +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty + +/** + * Wraps [delegate] and returns [defaultValue] when the underlying delegate's + * value is accessed, but the SharedPreferences instead does not contain the + * associated key. + */ +@InternalKrateApi +public class DelegateWithDefault( + private val delegate: KeyedKrateProperty, + private val defaultValue: T, +) : ReadWriteProperty { + override fun getValue(thisRef: Krate, property: KProperty<*>): T { + return if (delegate.key !in thisRef.sharedPreferences) { + defaultValue + } else { + delegate.getValue(thisRef, property)!! + } + } + + override fun setValue(thisRef: Krate, property: KProperty<*>, value: T) { + delegate.setValue(thisRef, property, value) + } +} + +/** + * Wraps the delegate provided by [propertyDelegateProvider] into a [DelegateWithDefault]. + */ +@InternalKrateApi +public class DelegateWithDefaultFactory( + private val propertyDelegateProvider: PropertyDelegateProvider>, + private val defaultValue: T, +) : PropertyDelegateProvider> { + + override fun provideDelegate(thisRef: Krate, property: KProperty<*>): ReadWriteProperty { + return DelegateWithDefault( + delegate = propertyDelegateProvider.provideDelegate(thisRef, property), + defaultValue = defaultValue + ) + } +} + +/** + * Adds a default value to a Krate delegate: if a Krate property's value + * not been set before, [defaultValue] is returned instead of `null`. + * + * Example property using this function: + * + * ```kotlin + * var username by stringPref("username").withDefault("Default User") + * ``` + */ +public fun KeyedKrateProperty.withDefault( + defaultValue: T +): ReadWriteProperty { + @OptIn(InternalKrateApi::class) + return DelegateWithDefault(this, defaultValue) +} + +/** + * Adds a default value to a Krate delegate: if a Krate property's value + * not been set before, [defaultValue] is returned instead of `null`. + * + * Example property using this function: + * + * ```kotlin + * var user by moshiPref("user").withDefault(User("Guest")) + * ``` + */ +public fun KeyedKratePropertyProvider.withDefault( + defaultValue: T +): PropertyDelegateProvider> { + @OptIn(InternalKrateApi::class) + return DelegateWithDefaultFactory(this, defaultValue) +} diff --git a/krate/src/main/kotlin/hu/autsoft/krate/default/FloatDelegateWithDefault.kt b/krate/src/main/kotlin/hu/autsoft/krate/default/FloatDelegateWithDefault.kt deleted file mode 100644 index f7498d0..0000000 --- a/krate/src/main/kotlin/hu/autsoft/krate/default/FloatDelegateWithDefault.kt +++ /dev/null @@ -1,21 +0,0 @@ -package hu.autsoft.krate.default - -import hu.autsoft.krate.Krate -import hu.autsoft.krate.util.edit -import kotlin.properties.ReadWriteProperty -import kotlin.reflect.KProperty - -internal class FloatDelegateWithDefault( - private val key: String, - private val default: Float, -) : ReadWriteProperty { - - override operator fun getValue(thisRef: Krate, property: KProperty<*>): Float { - return thisRef.sharedPreferences.getFloat(key, default) - } - - override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: Float) { - thisRef.sharedPreferences.edit { putFloat(key, value) } - } - -} diff --git a/krate/src/main/kotlin/hu/autsoft/krate/default/IntDelegateWithDefault.kt b/krate/src/main/kotlin/hu/autsoft/krate/default/IntDelegateWithDefault.kt deleted file mode 100644 index 2eea0d7..0000000 --- a/krate/src/main/kotlin/hu/autsoft/krate/default/IntDelegateWithDefault.kt +++ /dev/null @@ -1,21 +0,0 @@ -package hu.autsoft.krate.default - -import hu.autsoft.krate.Krate -import hu.autsoft.krate.util.edit -import kotlin.properties.ReadWriteProperty -import kotlin.reflect.KProperty - -internal class IntDelegateWithDefault( - private val key: String, - private val default: Int, -) : ReadWriteProperty { - - override operator fun getValue(thisRef: Krate, property: KProperty<*>): Int { - return thisRef.sharedPreferences.getInt(key, default) - } - - override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: Int) { - thisRef.sharedPreferences.edit { putInt(key, value) } - } - -} diff --git a/krate/src/main/kotlin/hu/autsoft/krate/default/LongDelegateWithDefault.kt b/krate/src/main/kotlin/hu/autsoft/krate/default/LongDelegateWithDefault.kt deleted file mode 100644 index 3ec5562..0000000 --- a/krate/src/main/kotlin/hu/autsoft/krate/default/LongDelegateWithDefault.kt +++ /dev/null @@ -1,21 +0,0 @@ -package hu.autsoft.krate.default - -import hu.autsoft.krate.Krate -import hu.autsoft.krate.util.edit -import kotlin.properties.ReadWriteProperty -import kotlin.reflect.KProperty - -internal class LongDelegateWithDefault( - private val key: String, - private val default: Long, -) : ReadWriteProperty { - - override operator fun getValue(thisRef: Krate, property: KProperty<*>): Long { - return thisRef.sharedPreferences.getLong(key, default) - } - - override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: Long) { - thisRef.sharedPreferences.edit { putLong(key, value) } - } - -} diff --git a/krate/src/main/kotlin/hu/autsoft/krate/default/StringDelegateWithDefault.kt b/krate/src/main/kotlin/hu/autsoft/krate/default/StringDelegateWithDefault.kt deleted file mode 100644 index 721fa1c..0000000 --- a/krate/src/main/kotlin/hu/autsoft/krate/default/StringDelegateWithDefault.kt +++ /dev/null @@ -1,22 +0,0 @@ -package hu.autsoft.krate.default - -import hu.autsoft.krate.Krate -import hu.autsoft.krate.util.edit -import kotlin.properties.ReadWriteProperty -import kotlin.reflect.KProperty - -internal class StringDelegateWithDefault( - private val key: String, - private val default: String, -) : ReadWriteProperty { - - @Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS") - override operator fun getValue(thisRef: Krate, property: KProperty<*>): String { - return thisRef.sharedPreferences.getString(key, default)!! - } - - override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: String) { - thisRef.sharedPreferences.edit { putString(key, value) } - } - -} diff --git a/krate/src/main/kotlin/hu/autsoft/krate/default/StringSetDelegateWithDefault.kt b/krate/src/main/kotlin/hu/autsoft/krate/default/StringSetDelegateWithDefault.kt deleted file mode 100644 index 3bd5a96..0000000 --- a/krate/src/main/kotlin/hu/autsoft/krate/default/StringSetDelegateWithDefault.kt +++ /dev/null @@ -1,22 +0,0 @@ -package hu.autsoft.krate.default - -import hu.autsoft.krate.Krate -import hu.autsoft.krate.util.edit -import kotlin.properties.ReadWriteProperty -import kotlin.reflect.KProperty - -internal class StringSetDelegateWithDefault( - private val key: String, - private val default: Set, -) : ReadWriteProperty> { - - @Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS") - override operator fun getValue(thisRef: Krate, property: KProperty<*>): Set { - return thisRef.sharedPreferences.getStringSet(key, default)!! - } - - override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: Set) { - thisRef.sharedPreferences.edit { putStringSet(key, value) } - } - -} diff --git a/krate/src/main/kotlin/hu/autsoft/krate/internal/InternalKrateApi.kt b/krate/src/main/kotlin/hu/autsoft/krate/internal/InternalKrateApi.kt index 2601bbb..152c0de 100644 --- a/krate/src/main/kotlin/hu/autsoft/krate/internal/InternalKrateApi.kt +++ b/krate/src/main/kotlin/hu/autsoft/krate/internal/InternalKrateApi.kt @@ -2,7 +2,7 @@ package hu.autsoft.krate.internal @RequiresOptIn( message = "This API is meant to be used by Krate's own modules, and third-party extensions to Krate. " + - "If you're a client of the Krate library, you probably shouldn't be using it.", + "If you're a client of the Krate library, you probably shouldn't be using it.", level = RequiresOptIn.Level.ERROR ) public annotation class InternalKrateApi diff --git a/krate/src/main/kotlin/hu/autsoft/krate/optional/BooleanDelegate.kt b/krate/src/main/kotlin/hu/autsoft/krate/optional/BooleanDelegate.kt index 0e54d1c..8867a36 100644 --- a/krate/src/main/kotlin/hu/autsoft/krate/optional/BooleanDelegate.kt +++ b/krate/src/main/kotlin/hu/autsoft/krate/optional/BooleanDelegate.kt @@ -1,14 +1,14 @@ package hu.autsoft.krate.optional import hu.autsoft.krate.Krate +import hu.autsoft.krate.base.KeyedKrateProperty +import hu.autsoft.krate.base.KeyedKratePropertyProvider import hu.autsoft.krate.util.edit -import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty internal class BooleanDelegate( - private val key: String, -) : ReadWriteProperty { - + override val key: String, +) : KeyedKrateProperty { override operator fun getValue(thisRef: Krate, property: KProperty<*>): Boolean? { return if (!thisRef.sharedPreferences.contains(key)) { null @@ -24,5 +24,12 @@ internal class BooleanDelegate( thisRef.sharedPreferences.edit { putBoolean(key, value) } } } +} +internal class BooleanDelegateProvider( + val key: String?, +) : KeyedKratePropertyProvider { + override fun provideDelegate(thisRef: Krate, property: KProperty<*>): BooleanDelegate { + return BooleanDelegate(key ?: property.name) + } } diff --git a/krate/src/main/kotlin/hu/autsoft/krate/optional/FloatDelegate.kt b/krate/src/main/kotlin/hu/autsoft/krate/optional/FloatDelegate.kt index 3d1285c..3a8ad3e 100644 --- a/krate/src/main/kotlin/hu/autsoft/krate/optional/FloatDelegate.kt +++ b/krate/src/main/kotlin/hu/autsoft/krate/optional/FloatDelegate.kt @@ -1,14 +1,14 @@ package hu.autsoft.krate.optional import hu.autsoft.krate.Krate +import hu.autsoft.krate.base.KeyedKrateProperty +import hu.autsoft.krate.base.KeyedKratePropertyProvider import hu.autsoft.krate.util.edit -import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty internal class FloatDelegate( - private val key: String, -) : ReadWriteProperty { - + override val key: String, +) : KeyedKrateProperty { override operator fun getValue(thisRef: Krate, property: KProperty<*>): Float? { return if (!thisRef.sharedPreferences.contains(key)) { null @@ -24,5 +24,12 @@ internal class FloatDelegate( thisRef.sharedPreferences.edit { putFloat(key, value) } } } +} +internal class FloatDelegateProvider( + val key: String?, +) : KeyedKratePropertyProvider { + override fun provideDelegate(thisRef: Krate, property: KProperty<*>): FloatDelegate { + return FloatDelegate(key ?: property.name) + } } diff --git a/krate/src/main/kotlin/hu/autsoft/krate/optional/IntDelegate.kt b/krate/src/main/kotlin/hu/autsoft/krate/optional/IntDelegate.kt index 7797cdb..d251171 100644 --- a/krate/src/main/kotlin/hu/autsoft/krate/optional/IntDelegate.kt +++ b/krate/src/main/kotlin/hu/autsoft/krate/optional/IntDelegate.kt @@ -1,14 +1,14 @@ package hu.autsoft.krate.optional import hu.autsoft.krate.Krate +import hu.autsoft.krate.base.KeyedKrateProperty +import hu.autsoft.krate.base.KeyedKratePropertyProvider import hu.autsoft.krate.util.edit -import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty internal class IntDelegate( - private val key: String, -) : ReadWriteProperty { - + override val key: String, +) : KeyedKrateProperty { override operator fun getValue(thisRef: Krate, property: KProperty<*>): Int? { return if (!thisRef.sharedPreferences.contains(key)) { null @@ -24,5 +24,12 @@ internal class IntDelegate( thisRef.sharedPreferences.edit { putInt(key, value) } } } +} +internal class IntDelegateProvider( + val key: String?, +) : KeyedKratePropertyProvider { + override fun provideDelegate(thisRef: Krate, property: KProperty<*>): IntDelegate { + return IntDelegate(key ?: property.name) + } } diff --git a/krate/src/main/kotlin/hu/autsoft/krate/optional/LongDelegate.kt b/krate/src/main/kotlin/hu/autsoft/krate/optional/LongDelegate.kt index fab65f3..00eb34a 100644 --- a/krate/src/main/kotlin/hu/autsoft/krate/optional/LongDelegate.kt +++ b/krate/src/main/kotlin/hu/autsoft/krate/optional/LongDelegate.kt @@ -1,14 +1,14 @@ package hu.autsoft.krate.optional import hu.autsoft.krate.Krate +import hu.autsoft.krate.base.KeyedKrateProperty +import hu.autsoft.krate.base.KeyedKratePropertyProvider import hu.autsoft.krate.util.edit -import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty internal class LongDelegate( - private val key: String, -) : ReadWriteProperty { - + override val key: String, +) : KeyedKrateProperty { override operator fun getValue(thisRef: Krate, property: KProperty<*>): Long? { return if (!thisRef.sharedPreferences.contains(key)) { null @@ -24,5 +24,12 @@ internal class LongDelegate( thisRef.sharedPreferences.edit { putLong(key, value) } } } +} +internal class LongDelegateProvider( + val key: String?, +) : KeyedKratePropertyProvider { + override fun provideDelegate(thisRef: Krate, property: KProperty<*>): LongDelegate { + return LongDelegate(key ?: property.name) + } } diff --git a/krate/src/main/kotlin/hu/autsoft/krate/optional/StringDelegate.kt b/krate/src/main/kotlin/hu/autsoft/krate/optional/StringDelegate.kt index 46fce62..b8ce783 100644 --- a/krate/src/main/kotlin/hu/autsoft/krate/optional/StringDelegate.kt +++ b/krate/src/main/kotlin/hu/autsoft/krate/optional/StringDelegate.kt @@ -1,14 +1,14 @@ package hu.autsoft.krate.optional import hu.autsoft.krate.Krate +import hu.autsoft.krate.base.KeyedKrateProperty +import hu.autsoft.krate.base.KeyedKratePropertyProvider import hu.autsoft.krate.util.edit -import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty internal class StringDelegate( - private val key: String, -) : ReadWriteProperty { - + override val key: String, +) : KeyedKrateProperty { override operator fun getValue(thisRef: Krate, property: KProperty<*>): String? { return if (!thisRef.sharedPreferences.contains(key)) { null @@ -24,5 +24,12 @@ internal class StringDelegate( thisRef.sharedPreferences.edit { putString(key, value) } } } +} +internal class StringDelegateProvider( + val key: String?, +) : KeyedKratePropertyProvider { + override fun provideDelegate(thisRef: Krate, property: KProperty<*>): StringDelegate { + return StringDelegate(key ?: property.name) + } } diff --git a/krate/src/main/kotlin/hu/autsoft/krate/optional/StringSetDelegate.kt b/krate/src/main/kotlin/hu/autsoft/krate/optional/StringSetDelegate.kt index 2f58ab5..da753c1 100644 --- a/krate/src/main/kotlin/hu/autsoft/krate/optional/StringSetDelegate.kt +++ b/krate/src/main/kotlin/hu/autsoft/krate/optional/StringSetDelegate.kt @@ -1,14 +1,14 @@ package hu.autsoft.krate.optional import hu.autsoft.krate.Krate +import hu.autsoft.krate.base.KeyedKrateProperty +import hu.autsoft.krate.base.KeyedKratePropertyProvider import hu.autsoft.krate.util.edit -import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty internal class StringSetDelegate( - private val key: String, -) : ReadWriteProperty?> { - + override val key: String, +) : KeyedKrateProperty?> { override operator fun getValue(thisRef: Krate, property: KProperty<*>): Set? { return if (!thisRef.sharedPreferences.contains(key)) { null @@ -24,5 +24,12 @@ internal class StringSetDelegate( thisRef.sharedPreferences.edit { putStringSet(key, value) } } } +} +internal class StringSetDelegateProvider( + val key: String?, +) : KeyedKratePropertyProvider?> { + override fun provideDelegate(thisRef: Krate, property: KProperty<*>): StringSetDelegate { + return StringSetDelegate(key ?: property.name) + } } diff --git a/krate/src/test/java/hu/autsoft/krate/TestKrate.kt b/krate/src/test/java/hu/autsoft/krate/TestKrate.kt index f9a11fc..9e30613 100644 --- a/krate/src/test/java/hu/autsoft/krate/TestKrate.kt +++ b/krate/src/test/java/hu/autsoft/krate/TestKrate.kt @@ -1,6 +1,7 @@ package hu.autsoft.krate import android.content.Context +import hu.autsoft.krate.default.withDefault import hu.autsoft.krate.validation.validate internal class TestKrate(context: Context) : SimpleKrate(context) { @@ -17,7 +18,8 @@ internal class TestKrate(context: Context) : SimpleKrate(context) { var optionalStringSet by stringSetPref("optionalStringSet") var optionalValidatedString by stringPref("optionalValidatedString") .validate { it?.length ?: 5 == 5 } - var defaultValidatedFloat by floatPref("defaultValidatedFloat", 0.0f) + var defaultValidatedFloat by floatPref("defaultValidatedFloat") + .withDefault(0.0f) .validate { it in 0.0f..1.0f } }