With this library dealing with application preferences becomes much more expressive and concise.
It is indented to be applied to kotlin projects in the first place, but nothing stops you from using it in the good old java apps. The core idea is to create a clean abstraction on top of the android shared preferences (or any other persistence mechanism) and provide a convenient and thread-safe API for reading, writing and observing preference values.
Bugfixing
Ability to clear preference and set its value back to the default
preference.clear()
An app preference of a primitive type can be declared with a single line of code
val myPreference by sharedPrefs.boolean(true)
Where sharedPrefs
is a reference to the android SharedPreferences
, and the default value of the property is set to true
.
Such minimalistic syntax is provided in kotlin by using Delegated Properties and Extension functions
Along with all primitive types that SharedPreferences
support it is also possible to store any type of a value by utilizing the SharedPreferences.generic(defaultValue, reader, writer)
extension method.
The use of this method is pretty straightforward. It takes 3 parameters: defaultValue
, reader
and writer
, where
-
defaultValue
is self-explanatory -
The
reader
is a function that is used to retrieve the data from aSharedPreferences
. It has the following signature:
SharedPreferences.(key: String, defaultValue: T) -> T
It takes a key of a type String
and a defaultValue
of a preference type as input parameters and should return a value of a corresponding type. For instance, this is a basic implementation of a reader that returns an instance of a Point
type:
{ key, defValue ->
val serializedValue = getString(key, null)
if (serializedValue == null) {
defValue
} else {
val components = serializedValue.split(',')
Point(components[0].toInt(), components[1].toInt())
}
}
- The
writer
function, in turn, has following signature:
SharedPreferences.(key: String, value: T) -> Unit
It is used to serialize the value and store it in the android SharedPreferences
. Continuing the example, the writer function will be:
{ key, value ->
val serializedValue = "${value.x},${value.y}"
edit().putString(key, serializedValue).apply()
}
So the complete declaration of an app preference of a type Point
would be something like this:
val pointPreference by prefs.generic(
Point(0,0),
{ key, defValue ->
val serializedValue = getString(key, null)
if (serializedValue == null) {
defValue
} else {
val components = serializedValue.split(',')
Point(components[0].toInt(), components[1].toInt())
}
},
{ key, value ->
val serializedValue = "${value.x},${value.y}"
edit().putString(key, serializedValue).apply()
}
)
Sometimes it makes sense to extract lambdas in order to reuse them multiple times.
To get the actual value of the preference call
val value = preference.get()
Similarly, to save new value just call
preference.set(value)
The most interesting part of this library is the ability to observe preference changes. Simply call
preference.observe { pref, value -> /* your code here */ }
and the lambda passed into the observe method will be called immediately with an actual preference value and subsequently every time the value changes.
If there is no need in receiving the initial preference value, the overloaded method to the rescue:
preference.observe(false) { pref, value -> /* your code here */ }
Please don't forget to call
preference.disconnect(observer)
to avoid possible memory leaks. Do not hesitate to use android studio profiler to verify the correctness of your code.
For convenience purposes, the Preference.observe()
returns an object of a Disposable
type, representing a connection between the preference and the listener.
This object has a single method called Disposable.dispose()
which automatically invokes preference.disconnect(observer)
, eliminating the need for keeping references to all preferences listeners.
Is it especially useful if there are plenty of listeners that should be disconnected altogether after, let's say onDestroy()
event.
Then the code would look something like this:
private val disposables = mutableListOf<Preference.Disposable>()
override fun onCreate() {
...
preference1
.observe { _, _ -> /* your code here */ }
.apply { disposables.add(this) }
preference2
.observe { _, _ -> /* your code here */ }
.apply { disposables.add(this) }
preference3
.observe { _, _ -> /* your code here */ }
.apply { disposables.add(this) }
}
override fun onDestroy() {
...
disposables.forEach { it.dispose() }
}
Much cleaner!
There are also a couple of extension methods that provide a neat way of binding application preferences directly to the android databinding observables.
Simply declare your databinding observable and then call
preference.connectTo(myDatabindingObservable)
That's it!
For a complete sample please refer to sample-kotlin or sample-java projects.