Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
48b51ac
Introduce KeyDelegate and KeyDelegateProvider
Sep 23, 2021
5d962ec
Introduce withDefault() extension instead of using default property
Sep 24, 2021
1936c24
Update Example app to follow API changes
Sep 24, 2021
2e4666a
Update README.md to follow the default value API changes
Sep 24, 2021
6cdcad3
Make key arguments of the delegate function nullable and use property…
Sep 24, 2021
844c2a6
Remove deprecated validated functions
zsmb13 Nov 17, 2021
5fb34fb
Convert KeyDelegate to an interface
zsmb13 Nov 17, 2021
aa8037f
Convert KeyDelegateProvider to an interface
zsmb13 Nov 17, 2021
994103d
Rename KeyedKrateProperty and KeyedKratePropertyProvider
zsmb13 Nov 17, 2021
7242c17
Merge pull request #29 from AutSoft/feature/update-deprecations
zsmb13 Nov 17, 2021
450e81d
Merge remote-tracking branch 'origin-github/dev' into feature/update_…
zsmb13 Nov 17, 2021
687c674
Format code
zsmb13 Nov 17, 2021
5d5e589
Raise deprecations to errors, remove unused impl functions
zsmb13 Nov 17, 2021
f5914a1
Fix type inference problems
zsmb13 Nov 17, 2021
1929646
Format primitive delegate constructors
zsmb13 Nov 17, 2021
f54bf23
Format code again
zsmb13 Nov 17, 2021
1bd2e3d
Add KDocs
zsmb13 Nov 17, 2021
d0e0d0d
Merge branch 'feature/update_default_values_api' into feature/default…
zsmb13 Nov 17, 2021
7b99aac
Use non-null keys in KeyedKrateProperty
zsmb13 Nov 17, 2021
3b54593
Rename default param to defaultValue
zsmb13 Nov 17, 2021
f9d62d9
Format and organize code
zsmb13 Nov 18, 2021
d1c8d41
Update replacements
zsmb13 Nov 18, 2021
c72dcfb
Improve kdocs
zsmb13 Nov 18, 2021
f807806
Clean app sample app changes
zsmb13 Nov 18, 2021
c6fdf13
Rewrite README sections around basic usage
zsmb13 Nov 18, 2021
0dcf201
Merge branch 'feature/update_default_values_api' into feature/default…
zsmb13 Nov 18, 2021
b974660
Update Kdocs
zsmb13 Nov 18, 2021
558cb5b
Update README with Custom keys section
zsmb13 Nov 18, 2021
0c63807
Update custom key code sample
zsmb13 Nov 18, 2021
3587036
Update README.md
zsmb13 Nov 20, 2021
35239cc
Add trailing commas ✨
zsmb13 Nov 20, 2021
bfc8d43
Merge pull request #26 from ivadyistvan/feature/update_default_values…
zsmb13 Nov 23, 2021
f291082
Merge remote-tracking branch 'origin-github/dev' into feature/default…
zsmb13 Nov 23, 2021
7574ee6
Merge pull request #27 from ivadyistvan/feature/default_keys
zsmb13 Nov 23, 2021
33494cb
Bump version number to 2.0.0
zsmb13 Nov 23, 2021
23d01ae
Update dependency versions
zsmb13 Nov 23, 2021
9399bbb
Add GitHub Actions status badge to README
zsmb13 Nov 23, 2021
7c8b1a6
Add note about possible breaking change when renaming a property
zsmb13 Nov 23, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 57 additions & 46 deletions README.md
Original file line number Diff line number Diff line change
@@ -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()

}

Expand All @@ -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 {
Expand All @@ -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)

}
```
Expand All @@ -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("")

}
```
Expand All @@ -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<String>`. You may of course want to store additional types in Krate.

Expand All @@ -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<Article>? by moshiPref("articles")
var user: User? by moshiPref()
var savedArticles: List<Article>? by moshiPref()
}
```

Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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<Article>? by kotlinxPref("articles")
var user: User? by kotlinxPref()
var savedArticles: List<Article>? by kotlinxPref()
}
```

Expand All @@ -197,7 +208,7 @@ class CustomKotlinxKrate(context: Context) : SimpleKrate(context) {
}
}

var user: User? by kotlinxPref("user")
var user: User? by kotlinxPref()
}
```

Expand All @@ -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<Article>? by gsonPref("articles")
var user: User? by gsonPref()
var savedArticles: List<Article>? by gsonPref()
}
```

Expand All @@ -226,7 +237,7 @@ class CustomGsonKrate(context: Context) : SimpleKrate(context) {
gson = GsonBuilder().create()
}

var user: User? by gsonPref("user")
var user: User? by gsonPref()
}
```

Expand Down
32 changes: 25 additions & 7 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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')
}
19 changes: 19 additions & 0 deletions app/src/main/java/hu/autsoft/krateexample/ExampleActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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() {

Expand Down Expand Up @@ -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() {
Expand All @@ -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(),
)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -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<User>().withDefault(User("Gson", "Green"))
override var exampleUserKotlinX by kotlinxPref<User>().withDefault(User("KotlinX", "Klark"))
override var exampleUserMoshi by moshiPref<User>().withDefault(User("Moshi", "Miller"))

}
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package hu.autsoft.krateexample.krates

import hu.autsoft.krateexample.models.User

interface ExampleSettings {
var exampleBoolean: Boolean
var exampleFloat: Float
var exampleInt: Int
var exampleLong: Long
var exampleString: String
var exampleStringSet: Set<String>
var exampleUserGson: User
var exampleUserKotlinX: User
var exampleUserMoshi: User
}
Loading