Skip to content

Instantly share code, notes, and snippets.

@AChep
Last active June 7, 2018 08:10
Show Gist options
  • Save AChep/4e665b2d3be1604954b6e0c82c0c227e to your computer and use it in GitHub Desktop.
Save AChep/4e665b2d3be1604954b6e0c82c0c227e to your computer and use it in GitHub Desktop.
Base class for a simple config
import android.content.Context
import android.content.SharedPreferences
import android.os.Looper
import kotlin.properties.ObservableProperty
import kotlin.reflect.KProperty
/**
* @author Artem Chepurnoy
*/
abstract class ConfigBase {
private var isInitializing = false
private var editor: SharedPreferences.Editor? = null
private val changes: MutableSet<String> = HashSet()
private val listeners: MutableList<OnConfigChangedListener> = ArrayList()
/**
* Should be called on application create method,
* only once.
*/
fun init(context: Context) {
val map = getConfigSharedPreferences(context).all
// Maps the saved values to actual
// variables.
isInitializing = true
onInitVars(map)
isInitializing = false
}
abstract fun onInitVars(map: Map<String, *>)
/**
* Call before writing to config properties, otherwise you
* will get a [NullPointerException].
*/
fun edit(context: Context, block: ConfigBase.() -> Unit) {
synchronized(this) {
try {
getConfigSharedPreferences(context).edit().let {
editor = it
block.invoke(this@ConfigBase)
it.apply()
}
} finally {
editor = null
}
if (Looper.getMainLooper().isCurrentThread) {
// Create a copy of changes
val copy = HashSet(changes)
changes.clear()
// Notify about the changes
notifyChanges(copy)
} else throw IllegalStateException("Editing the config is not supported from other threads")
}
}
private fun notifyChanges(changes: Set<String>) {
synchronized(listeners) {
changes.forEach { key ->
// Notify each of the listeners
// about this particular change
listeners.forEach { it.onConfigChanged(key) }
}
}
}
/**
* Adds the listener to the list of active listeners;
* dont forget to remove it later.
* @see removeListener
*/
fun addListener(listener: OnConfigChangedListener) {
synchronized(listeners) {
listeners += listener
}
}
/**
* Removes the listener from the list of active listeners.
* @see addListener
*/
fun removeListener(listener: OnConfigChangedListener) {
synchronized(listeners) {
listeners -= listener
}
}
fun <T> configProperty(key: String, initialValue: T) = ConfigProperty(key, initialValue)
protected abstract fun getConfigSharedPreferences(context: Context): SharedPreferences
/**
* @author Artem Chepurnoy
*/
inner class ConfigProperty<T>(
private val key: String,
initialValue: T
) : ObservableProperty<T>(initialValue) {
override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) {
if (isInitializing) return
// Register the current change in the system, so
// we know what to notify.
changes.add(key)
// Update the editor
editor!!.apply {
when (newValue) {
is Int -> putInt(key, newValue)
is Long -> putLong(key, newValue)
is String -> putString(key, newValue)
}
}
}
}
/**
* @author Artem Chepurnoy
*/
interface OnConfigChangedListener {
fun onConfigChanged(key: String)
}
/**
* A type-safe version of [Map.getOrDefault]. Returns the value corresponding
* to the given `key`, or `defaultValue` if such a key is not present in the map.
*/
inline fun <reified T, K> Map<K, *>.getOrDefaultTs(key: K, defaultValue: T) = this[key] as? T
?: defaultValue
}
@AChep
Copy link
Author

AChep commented Jun 7, 2018

Create your config

object Config : ConfigBase() {

    const val KEY_ACCENT_COLOR = "accent"
    // Layout
    const val KEY_LAYOUT = "layout"
    const val LAYOUT_HORIZONTAL = "layout::horizontal"
    const val LAYOUT_VERTICAL = "layout::vertical"
    // Theme
    const val KEY_THEME = "theme"
    const val THEME_BLACK = "BLACK"
    const val THEME_DARK = "DARK"
    const val THEME_LIGHT = "LIGHT"

    var accentColor: Int by configProperty(KEY_ACCENT_COLOR, PALETTE_BLUE)
    var themeName: String by configProperty(KEY_THEME, THEME_BLACK)
    var layoutName: String by configProperty(KEY_LAYOUT, LAYOUT_VERTICAL)

    override fun onInitVars(map: Map<String, *>) {
        accentColor = map.getOrDefaultTs(KEY_ACCENT_COLOR, accentColor)
        layoutName = map.getOrDefaultTs(KEY_LAYOUT, layoutName)
        themeName = map.getOrDefaultTs(KEY_THEME, themeName)
    }

    override fun getConfigSharedPreferences(context: Context): SharedPreferences {
        return context.getSharedPreferences("config", 0)
    }

}

and initialize it on application create

    override fun onCreate() {
        super.onCreate()
        Config.init(this)
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment