Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preferences API extensions #255

Merged
merged 7 commits into from
Mar 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
- **[FEATURE]** (`ktx-async`) Added `RenderingScope` factory function for custom scopes using rendering thread dispatcher.
- **[FEATURE]** (`ktx-math`) Added `lerp` and `interpolate` extension functions for `Float` ranges.
- **[FEATURE]** (`ktx-vis`) Added `image` (`VisImage`) factory methods consuming `Texture`, `TextureRegion` and `NinePatch`.
- **[FEATURE]** (`ktx-preferences`) Added a new KTX module: Preferences API extensions.
czyzby marked this conversation as resolved.
Show resolved Hide resolved
- Added `set` operators for String, Int, Float, Long, Boolean, Pair<String, Any> and Any
- Added `get` operator
- Added `flush` extension function that takes additional `Preferences` calls as a Lambda

#### 1.9.10-b4

Expand Down
84 changes: 84 additions & 0 deletions preferences/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
[![Maven Central](https://img.shields.io/maven-central/v/io.github.libktx/ktx-collections.svg)](https://search.maven.org/artifact/io.github.libktx/ktx-collections)

# KTX: preference utilities

Utilities and extension function for LibGDX preferences.

### Why?

LibGDX [Preferences](https://github.com/libgdx/libgdx/wiki/Preferences) do not support a generic way to
set and get values. Since they work very similar to a Map, they should ideally support a similar
syntax. Especially with Kotlin we can use the advantage of square bracket operators.

### Guide

- Values can be set via new `set` operators using the square bracket syntax. It is no longer needed
to call type specific methods like `putString` or `putBoolean`. `Set` already supports any type.
- Values can be retrieved via new generic `get` operator.
- Preferences now support `Pair<String, Any>` parameter. Values can be set by using the `infix to` function
- New `flush` extension that supports a Lambda to easily update the Preferences before flushing.
- `set` and `get` support objects of any type. If the type is not a String, Boolean, Int, Float or Long value
then the value is stored and retrieved using [Json](https://github.com/libgdx/libgdx/wiki/Reading-and-writing-JSON).
.
### Usage examples

```kotlin
import com.badlogic.gdx.Gdx
import ktx.preferences.flush
import ktx.preferences.get
import ktx.preferences.set

private class Player(val name: String = "Player 1", val life: Int = 100)

fun main() {
val prefs = Gdx.app.getPreferences("MyPreferences")

// set values with new set operator functions
prefs["Key1"] = "someStringValue"
prefs["Key2"] = 1
prefs["Key3"] = 1.23f
prefs["Key4"] = 100L
prefs["Key5"] = true
// Any classes are automatically converted to a Json string
prefs["Key6"] = Player()

prefs.run {
// use Pair<String, Any> to update preference values
set("Key7" to 123)
set("Key8" to "someOtherStringValue")
}

// get values with new get operator function
val value1: String = prefs["Key1"]
val value2: Int = prefs["Key2"]
val value3: Float = prefs["Key3"]
val value4: Long = prefs["Key4"]
val value5: Boolean = prefs["Key5"]
// Any classes are automatically loaded from a Json string
val value6: Player = prefs["Key6"]
val value7: Int = prefs["Key7"]
val value8: String = prefs["Key8"]

println(value1) // prints 'someStringValue'
println(value2) // prints 1
println(value3) // prints 1.23
println(value4) // prints 100
println(value5) // prints true
println("${value6.name} - ${value6.life}") // prints 'Player 1 - 100'
println(value7) // prints 123
println(value8) // prints 'someOtherStringValue'

// adjust preferences before calling flush
// Key9 and Key10 will be flushed as well
prefs.flush {
set("Key9" to 10000)
set("Key10" to true)
}
}
```

### Additional documentation

- [LibGDX Json](https://github.com/libgdx/libgdx/wiki/Reading-and-writing-JSON)
- [LibGDX Preferences](https://github.com/libgdx/libgdx/wiki/Preferences)
- [Kotlin infix to function for Pair](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/to.html)
3 changes: 3 additions & 0 deletions preferences/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
dependencies {
provided "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
}
2 changes: 2 additions & 0 deletions preferences/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
projectName=ktx-preferences
projectDesc=LibGDX preferences utilities for applications developed with Kotlin.
75 changes: 75 additions & 0 deletions preferences/src/main/kotlin/ktx/preferences/preferences.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package ktx.preferences

import com.badlogic.gdx.Preferences
import com.badlogic.gdx.utils.Json

/**
* Stores a String **value** under the given **key** in the [Preferences].
*/
operator fun Preferences.set(key: String, value: String): Preferences = putString(key, value)

/**
* Stores a Boolean **value** under the given **key** in the [Preferences].
*/
operator fun Preferences.set(key: String, value: Boolean): Preferences = putBoolean(key, value)

/**
* Stores an Int **value** under the given **key** in the [Preferences].
*/
operator fun Preferences.set(key: String, value: Int): Preferences = putInteger(key, value)

/**
* Stores a Long **value** under the given **key** in the [Preferences].
*/
operator fun Preferences.set(key: String, value: Long): Preferences = putLong(key, value)

/**
* Stores a Float **value** under the given **key** in the [Preferences].
*/
operator fun Preferences.set(key: String, value: Float): Preferences = putFloat(key, value)

/**
* Stores Any **value** under the given **key** as Json string in the [Preferences].
*/
operator fun Preferences.set(key: String, value: Any): Preferences = putString(key, Json().toJson(value))

/**
* Stores Any **value** under the given **key** in the [Preferences]. If the value is not of type
* String, Boolean, Int, Float or Long then it will be stored as a Json string by creating a **new
* Json instance**.
*/
fun Preferences.set(pair: Pair<String, Any>): Preferences {
return when (pair.second) {
is String -> putString(pair.first, pair.second as String)
is Boolean -> putBoolean(pair.first, pair.second as Boolean)
is Int -> putInteger(pair.first, pair.second as Int)
is Float -> putFloat(pair.first, pair.second as Float)
is Long -> putLong(pair.first, pair.second as Long)
else -> putString(pair.first, Json().toJson(pair.second))
}
}

/**
* Retrieves a value from the [Preferences] for the given **key**. If the value is not of type
* String, Boolean, Int, Float or Long then it will be retrieved as Json string for the given **type**
* by creating a **new Json instance**.
*/
inline operator fun <reified T> Preferences.get(key: String): T {
return when (T::class) {
String::class -> getString(key) as T
Boolean::class -> getBoolean(key) as T
Int::class -> getInteger(key) as T
Float::class -> getFloat(key) as T
Long::class -> getLong(key) as T
else -> Json().fromJson(T::class.java, getString(key))
}
}

/**
* Calls [Preferences.flush] after executing the given **operations**.
* Operations can be any function of the [Preferences] class.
*/
inline fun Preferences.flush(operations: Preferences.() -> Unit) {
operations()
flush()
}
211 changes: 211 additions & 0 deletions preferences/src/test/kotlin/ktx/preferences/preferencesTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
package ktx.preferences

import com.badlogic.gdx.Preferences
import com.badlogic.gdx.utils.Json
import com.badlogic.gdx.utils.ObjectSet
import org.junit.Assert
import org.junit.Before
import org.junit.Test

class TestPreferences : Preferences {
private val map = HashMap<String, Any>()
var flushed = false

override fun contains(key: String) = map.containsKey(key)

override fun getBoolean(key: String) = map[key] as Boolean

override fun getBoolean(key: String, defValue: Boolean) = map.getOrDefault(key, defValue) as Boolean

override fun clear() = map.clear()

override fun putLong(key: String, value: Long): Preferences {
map[key] = value
return this
}

override fun put(vals: MutableMap<String, *>): Preferences {
vals.forEach { map[it.key] = it.value as Any }
return this
}

override fun putInteger(key: String, value: Int): Preferences {
map[key] = value
return this
}

override fun remove(key: String) {
map.remove(key)
}

override fun putBoolean(key: String, value: Boolean): Preferences {
map[key] = value
return this
}

override fun flush() {
flushed = true
}

override fun getInteger(key: String) = map[key] as Int

override fun getInteger(key: String, defValue: Int) = map.getOrDefault(key, defValue) as Int

override fun getLong(key: String) = map[key] as Long

override fun getLong(key: String, defValue: Long) = map.getOrDefault(key, defValue) as Long

override fun getFloat(key: String) = map[key] as Float

override fun getFloat(key: String, defValue: Float) = map.getOrDefault(key, defValue) as Float

override fun putFloat(key: String, value: Float): Preferences {
map[key] = value
return this
}

override fun getString(key: String) = map[key] as String

override fun getString(key: String, defValue: String) = map.getOrDefault(key, defValue) as String

override fun get(): MutableMap<String, *> = map

override fun putString(key: String, value: String): Preferences {
map[key] = value
return this
}
}

class PreferencesTest {
private lateinit var preferences: TestPreferences

@Before
fun `setup preferences`() {
preferences = TestPreferences()
}

@Test
fun `put string value`() {
preferences["Key"] = "Value"

Assert.assertTrue("Key" in preferences)
Assert.assertEquals("Value", preferences.getString("Key"))
}

@Test
fun `put boolean value`() {
preferences["Key"] = true

Assert.assertTrue("Key" in preferences)
Assert.assertEquals(true, preferences.getBoolean("Key"))
}

@Test
fun `put int value`() {
preferences["Key"] = 1

Assert.assertTrue("Key" in preferences)
Assert.assertEquals(1, preferences.getInteger("Key"))
}

@Test
fun `put float value`() {
preferences["Key"] = 1f

Assert.assertTrue("Key" in preferences)
Assert.assertEquals(1f, preferences.getFloat("Key"))
}

@Test
fun `put long value`() {
preferences["Key"] = 1L

Assert.assertTrue("Key" in preferences)
Assert.assertEquals(1L, preferences.getLong("Key"))
}

@Test
fun `put values as pairs`() {
preferences.set("Key1" to "Value")
preferences.set("Key2" to 1)

Assert.assertEquals("Value", preferences.getString("Key1"))
Assert.assertEquals(1, preferences.getInteger("Key2"))
}

@Test
fun `put Any value`() {
preferences["Key"] = ObjectSet<Any>()

Assert.assertTrue("Key" in preferences)
Assert.assertEquals(ObjectSet<Any>(), Json().fromJson(ObjectSet::class.java, preferences.getString("Key")))
}

@Test
fun `get string value`() {
preferences["Key"] = "Value"

val result: String = preferences["Key"]

Assert.assertEquals("Value", result)
}

@Test
fun `get boolean value`() {
preferences["Key"] = true

val result: Boolean = preferences["Key"]

Assert.assertEquals(true, result)
}

@Test
fun `get int value`() {
preferences["Key"] = 1

val result: Int = preferences["Key"]

Assert.assertEquals(1, result)
}

@Test
fun `get float value`() {
preferences["Key"] = 1f

val result: Float = preferences["Key"]

Assert.assertEquals(1f, result)
}

@Test
fun `get long value`() {
preferences["Key"] = 1L

val result: Long = preferences["Key"]

Assert.assertEquals(1L, result)
}

@Test
fun `get Any value`() {
preferences["Key"] = ObjectSet<Any>()

val result: ObjectSet<Any> = preferences["Key"]

Assert.assertEquals(ObjectSet<Any>(), result)
}

@Test
fun `flush changes`() {
preferences.flush {
this["Key1"] = "Value1"
this["Key2"] = 1
}

Assert.assertTrue(preferences.flushed)
Assert.assertTrue("Key1" in preferences)
Assert.assertTrue("Value1" == preferences.getString("Key1"))
Assert.assertTrue("Key2" in preferences)
Assert.assertTrue(1 == preferences.getInteger("Key2"))
}
}
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ include(
'inject',
'log',
'math',
'preferences',
'scene2d',
'style',
'tiled',
Expand Down