Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
rumboalla committed Mar 19, 2024
1 parent 2961e5d commit fb1befa
Show file tree
Hide file tree
Showing 35 changed files with 1,172 additions and 0 deletions.
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
*.iml
.gradle
/local.properties
/.idea
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
53 changes: 53 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# KryptoStore
KryptoStore is thin wrapper around Jetpack DataStore Preferences that provides useful features.

## Features
* Small
* Easily work with primitive preferences
* Serialization for complex objects
* Encryption

## Basic Usage
Import library: TODO
```kotlin
implementation("TODO")
```
Use preferences. Supported preferences: booleanPref, intPref, floatPref, doublePref, stringPref, stringSetPref.
```kotlin
val Context.store: DataStore<Preferences> by preferencesDataStore(name = "prefs")
val pref = booleanPref(context.store, "testBoolean", false)
val value = pref.get()
pref.set(true)
```

## Advanced Usage
Import library for serialization
```kotlin
implementation("TODO")
```
Create serialized preferences:
```kotlin
data class TestData(val one: String, val two: Double)
val Context.store: DataStore<Preferences> by preferencesDataStore(name = "prefs")
val pref = gsonPref(context.store, "testGsonPrefData", TestData("Don't Panic", 42.0), Gson())
val value = pref.get()
pref.set(TestData("Mostly Harmless", 43.0))
```

## Encryption
Import library for encryption
```kotlin
implementation("TODO")
```
Create encrypted preferences:
```kotlin
data class TestData(val one: String, val two: Double)
val Context.store: DataStore<Preferences> by preferencesDataStore(name = "prefs")
val pref = encryptedKeystorePref(context.store, "testEncryptedPrefData", TestData("Don't Panic", 42.0), createGsonTransform(gson))
val value = pref.get()
pref.set(TestData("Mostly Harmless", 43.0))
```

## TODO
* More serialization options: Moshi, kotlinx.serialization, etc...
* More encryption options.
4 changes: 4 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
plugins {
id("com.android.application") version "8.3.0" apply false
id("org.jetbrains.kotlin.android") version "1.9.22" apply false
}
1 change: 1 addition & 0 deletions core/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
54 changes: 54 additions & 0 deletions core/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
id("maven-publish")
}

android {
namespace = "com.github.rumboalla.kryptostore"
compileSdk = 34

defaultConfig {
minSdk = 16
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
isMinifyEnabled = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

kotlinOptions {
jvmTarget = "1.8"
}

testOptions {
targetSdk = 34
}
}

dependencies {
api("androidx.datastore:datastore-preferences:1.0.0")

testImplementation("junit:junit:4.13.2")

androidTestImplementation("androidx.test:runner:1.5.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
}

publishing {
publications {
create<MavenPublication>("maven") {
groupId = "com.github.rumboalla.kryptostore"
artifactId = "core"
version = "0.1.0"
}
}
}
Empty file added core/proguard-rules.pro
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package com.github.rumboalla.kryptostore

import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.preferencesDataStore
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.github.rumboalla.kryptostore.annotation.DangerousApi
import com.github.rumboalla.kryptostore.preference.booleanPref
import com.github.rumboalla.kryptostore.preference.doublePref
import com.github.rumboalla.kryptostore.preference.floatPref
import com.github.rumboalla.kryptostore.preference.genPref
import com.github.rumboalla.kryptostore.preference.intPref
import com.github.rumboalla.kryptostore.preference.stringPref
import com.github.rumboalla.kryptostore.preference.stringSetPref
import com.github.rumboalla.kryptostore.transform.Transform
import junit.framework.TestCase.assertEquals
import kotlinx.coroutines.runBlocking
import org.junit.Test
import org.junit.runner.RunWith


private val Context.store: DataStore<Preferences> by preferencesDataStore(name = "test")

@RunWith(AndroidJUnit4::class)
class KryptoStoreInstrumentedTest {

private val context = InstrumentationRegistry.getInstrumentation().targetContext

init {
// Clears the datastore
runBlocking { context.store.edit { it.clear() } }
}

@OptIn(DangerousApi::class)
@Test
fun testBoolean() = runBlocking {
val pref = booleanPref(context.store, "testBoolean", false)
assertEquals(pref.get(), false)
pref.set(true)
assertEquals(pref.get(), true)
pref.set(false)
assertEquals(pref.getBlocking(), false)
}

@OptIn(DangerousApi::class)
@Test
fun testInt() = runBlocking {
val pref = intPref(context.store, "testInt", 0)
assertEquals(pref.get(), 0)
pref.set(1)
assertEquals(pref.get(), 1)
pref.set(2)
assertEquals(pref.getBlocking(), 2)
}

@OptIn(DangerousApi::class)
@Test
fun testFloat() = runBlocking {
val pref = floatPref(context.store, "testFloat", 0f)
assertEquals(pref.get(), 0f)
pref.set(1f)
assertEquals(pref.get(), 1f)
pref.set(2f)
assertEquals(pref.getBlocking(), 2f)
}

@OptIn(DangerousApi::class)
@Test
fun testDouble() = runBlocking {
val pref = doublePref(context.store, "testDouble", 0.0)
assertEquals(pref.get(), 0.0)
pref.set(1.0)
assertEquals(pref.get(), 1.0)
pref.set(2.0)
assertEquals(pref.getBlocking(), 2.0)
}

@OptIn(DangerousApi::class)
@Test
fun testString() = runBlocking {
val pref = stringPref(context.store, "testString", "42")
assertEquals(pref.get(), "42")
pref.set("Don't Panic")
assertEquals(pref.get(), "Don't Panic")
pref.set("Mostly Harmless")
assertEquals(pref.getBlocking(), "Mostly Harmless")
}

@OptIn(DangerousApi::class)
@Test
fun testStringSet() = runBlocking {
val pref = stringSetPref(context.store, "testStringSet", setOf("42", "Don't Panic", "Mostly Harmless"))
assertEquals(pref.get(), setOf("42", "Don't Panic", "Mostly Harmless"))
pref.set(setOf("Don't Panic", "Mostly Harmless", "42"))
assertEquals(pref.get(), setOf("Don't Panic", "Mostly Harmless", "42"))
pref.set(setOf("Mostly Harmless", "42", "Don't Panic"))
assertEquals(pref.getBlocking(), setOf("Mostly Harmless", "42", "Don't Panic"))
}

@OptIn(DangerousApi::class)
@Test
fun testGenPref() = runBlocking {
val transform = object : Transform<Float> {
override fun transform(t: Float): String = t.toString()
override fun transform(t: String): Float = t.toFloat()
}
val pref = genPref(context.store, "genPref", 0f, transform)
assertEquals(pref.get(), 0f)
pref.set(1f)
assertEquals(pref.get(), 1f)
pref.set(2f)
assertEquals(pref.getBlocking(), 2f)
}
}
2 changes: 2 additions & 0 deletions core/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest/>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.github.rumboalla.kryptostore.annotation


@RequiresOptIn("This is a dangerous call. Please make sure you understand how it works.")
@Retention(AnnotationRetention.BINARY)
annotation class DangerousApi
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.github.rumboalla.kryptostore.preference

import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import com.github.rumboalla.kryptostore.transform.Transform


open class GenericPreference<T>(
private val store: DataStore<Preferences>,
name: String,
private val defValue: T,
private val transform: Transform<T>
): Preference<T> {
private val key = stringPreferencesKey(name)
private val flow = store.data
.map { it[key] ?: transform.transform(defValue) }
.map { transform.transform(it) }

override suspend fun get() = flow.first()
override suspend fun set(v: T) { store.edit { it[key] = transform.transform(v) } }
override fun flow() = flow
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.github.rumboalla.kryptostore.preference

import com.github.rumboalla.kryptostore.annotation.DangerousApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.runBlocking


interface Preference<T> {

/**
* Gets the value of a preference.
*
* @return {@T} Value of the preference.
*/
suspend fun get(): T

/**
* Sets the value of a preference.
*
* @param v {@T} Value {@T} to set in the preference.
*/
suspend fun set(v: T)

/**
* Returns a flow of the preference.
*
* @return {@Flow<T>} Flow of the preference.
*/
fun flow(): Flow<T>

/**
* Gets the value of the preference. This call blocks until the preference is retrieved.
*
* @return {@T} Value of the preference.
*/
@DangerousApi
fun getBlocking() = runBlocking { get() }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.github.rumboalla.kryptostore.preference

import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.doublePreferencesKey
import androidx.datastore.preferences.core.floatPreferencesKey
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.core.stringSetPreferencesKey
import com.github.rumboalla.kryptostore.transform.Transform


fun intPref(store: DataStore<Preferences>, name: String, defValue: Int) =
PrimitivePreference(store, intPreferencesKey(name), defValue)

fun stringPref(store: DataStore<Preferences>, name: String, defValue: String) =
PrimitivePreference(store, stringPreferencesKey(name), defValue)

fun booleanPref(store: DataStore<Preferences>, name: String, defValue: Boolean) =
PrimitivePreference(store, booleanPreferencesKey(name), defValue)

fun doublePref(store: DataStore<Preferences>, name: String, defValue: Double) =
PrimitivePreference(store, doublePreferencesKey(name), defValue)

fun floatPref(store: DataStore<Preferences>, name: String, defValue: Float) =
PrimitivePreference(store, floatPreferencesKey(name), defValue)

fun stringSetPref(store: DataStore<Preferences>, name: String, defValue: Set<String>) =
PrimitivePreference(store, stringSetPreferencesKey(name), defValue)

fun <T> genPref(store: DataStore<Preferences>, name: String, defValue: T, transform: Transform<T>) =
GenericPreference(store, name, defValue, transform)
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.github.rumboalla.kryptostore.preference

import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map


open class PrimitivePreference<T>(
private val store: DataStore<Preferences>,
private val key: Preferences.Key<T>,
private val defValue: T
): Preference<T> {
private val flow = store.data.map { it[key] ?: defValue }

override suspend fun get() = flow.first()
override suspend fun set(v: T) { store.edit { it[key] = v } }
override fun flow() = flow
}
Loading

0 comments on commit fb1befa

Please sign in to comment.