Skip to content

Instantly share code, notes, and snippets.

@kibotu
Last active February 15, 2021 16:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kibotu/9e207e5726b819f4a00990c7ee3dc479 to your computer and use it in GitHub Desktop.
Save kibotu/9e207e5726b819f4a00990c7ee3dc479 to your computer and use it in GitHub Desktop.
Delegate Properties
import android.os.Binder
import android.os.Bundle
import android.os.Parcelable
import android.view.View
import androidx.core.app.BundleCompat
import androidx.fragment.app.Fragment
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
/**
* Eases the Fragment.newInstance ceremony by marking the fragment's args with this delegate
* Just write the property in newInstance and read it like any other property after the fragment has been created
*
* Inspired by Adam Powell, he mentioned it during his IO/17 talk about Kotlin
*
* @source https://gist.github.com/yanngx/efdfbf777d21d6f0e73fab4efe47e924
*/
class FragmentArgumentDelegate<T : Any?> : ReadWriteProperty<Fragment, T?> {
var value: T? = null
override operator fun getValue(thisRef: Fragment, property: KProperty<*>): T? {
if (value == null) {
val args = thisRef.arguments
@Suppress("UNCHECKED_CAST")
value = args?.get(property.name) as? T
}
return value
}
override operator fun setValue(thisRef: Fragment, property: KProperty<*>, value: T?) {
var args = thisRef.arguments
if (args == null) {
args = Bundle()
thisRef.arguments = args
}
val key = property.name
with(args) {
when (value) {
is Boolean -> putBoolean(key, value)
is Byte -> putByte(key, value)
is Char -> putChar(key, value)
is Short -> putShort(key, value)
is Int -> putInt(key, value)
is Long -> putLong(key, value)
is Float -> putFloat(key, value)
is Double -> putDouble(key, value)
is String -> putString(key, value)
is CharSequence -> putCharSequence(key, value)
// is ArrayList<Int> -> putIntegerArrayList(key, value)
// is ArrayList<String> -> putStringArrayList(key, value)
// is ArrayList<CharSequence> -> putCharSequenceArrayList(key, value)
is java.io.Serializable -> putSerializable(key, value)
is BooleanArray -> putBooleanArray(key, value)
is ByteArray -> putByteArray(key, value)
is ShortArray -> putShortArray(key, value)
is CharArray -> putCharArray(key, value)
is IntArray -> putIntArray(key, value)
is LongArray -> putLongArray(key, value)
is FloatArray -> putFloatArray(key, value)
is DoubleArray -> putDoubleArray(key, value)
(value as? Array<*>)?.isArrayOf<String>() -> {
@Suppress("UNCHECKED_CAST")
putStringArray(key, (value as Array<String>))
}
(value as? Array<*>)?.isArrayOf<CharSequence>() -> {
@Suppress("UNCHECKED_CAST")
putCharSequenceArray(key, (value as Array<CharSequence>))
}
is Bundle -> putBundle(key, value)
is Binder -> BundleCompat.putBinder(args, key, value)
is Parcelable ->
putParcelable(key, value)
else -> throw IllegalStateException("Type $value of property ${property.name} is not supported")
}
}
}
}
inline fun <reified T : Any?> Fragment.bundle(): ReadWriteProperty<Fragment, T?> = FragmentArgumentDelegate()
class MyFragment : Fragment(){
var email by bundle<String?>()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val x = email
val y = arguments?.getString("email")
}
}
import android.view.View
import androidx.fragment.app.Fragment
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.Observer
import androidx.viewbinding.ViewBinding
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
class FragmentViewBindingDelegate<T : ViewBinding>(
val fragment: Fragment,
val viewBindingFactory: (View) -> T
) : ReadOnlyProperty<Fragment, T> {
private var binding: T? = null
init {
fragment.lifecycle.addObserver(object : DefaultLifecycleObserver {
val viewLifecycleOwnerLiveDataObserver =
Observer<LifecycleOwner?> {
val viewLifecycleOwner = it ?: return@Observer
viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onDestroy(owner: LifecycleOwner) {
binding = null
}
})
}
override fun onCreate(owner: LifecycleOwner) {
fragment.viewLifecycleOwnerLiveData.observeForever(viewLifecycleOwnerLiveDataObserver)
}
override fun onDestroy(owner: LifecycleOwner) {
fragment.viewLifecycleOwnerLiveData.removeObserver(viewLifecycleOwnerLiveDataObserver)
}
})
}
override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
val binding = binding
if (binding != null) {
return binding
}
val lifecycle = fragment.viewLifecycleOwner.lifecycle
if (!lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) {
throw IllegalStateException("Should not attempt to get bindings when Fragment views are destroyed.")
}
return viewBindingFactory(thisRef.requireView()).also { this.binding = it }
}
}
fun <T : ViewBinding> Fragment.viewBinding(viewBindingFactory: (View) -> T) =
FragmentViewBindingDelegate(this, viewBindingFactory)
class F : Fragment(){
val binding by viewBinding { }
}
import android.content.SharedPreferences
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
private class SharedPreferenceDelegate<T>(
private val sharedPreferences: SharedPreferences?,
private val key: String,
private val defaultValue: T,
private val isCachingEnabled: Boolean = true
) : ReadWriteProperty<Any, T> {
private var value: T? = null
private val accessLock = Any()
init {
if (sharedPreferences == null) {
Timber.w("sharedPreferences is null - can't read or save values")
}
}
@Suppress("UNCHECKED_CAST")
override fun getValue(thisRef: Any, property: KProperty<*>): T = synchronized(accessLock) {
if (isCachingEnabled && value != null) {
return value as T
}
value = when (defaultValue) {
is Boolean -> sharedPreferences?.getBoolean(key, defaultValue) ?: defaultValue
is Int -> sharedPreferences?.getInt(key, defaultValue) ?: defaultValue
is Long -> sharedPreferences?.getLong(key, defaultValue) ?: defaultValue
is Float -> sharedPreferences?.getFloat(key, defaultValue) ?: defaultValue
is String -> sharedPreferences?.getString(key, defaultValue) ?: defaultValue
else -> throw IllegalArgumentException("Type not supported")
} as T
return value as T
}
override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
when (defaultValue) {
is Boolean -> sharedPreferences?.edit()?.putBoolean(key, value as Boolean)?.apply()
is Int -> sharedPreferences?.edit()?.putInt(key, value as Int)?.apply()
is Long -> sharedPreferences?.edit()?.putLong(key, value as Long)?.apply()
is Float -> sharedPreferences?.edit()?.putFloat(key, value as Float)?.apply()
is String -> sharedPreferences?.edit()?.putString(key, value as String)?.apply()
else -> throw IllegalArgumentException("Type not supported")
}
this.value = value
}
}
/**
* Create a [ReadWriteProperty] which is saving the data into an [SharedPreferences].
* @param sharedPreferences the instance of encrypted [SharedPreferences] which will be used for read and write of the property
* @param key for saving the data in [SharedPreferences]
* @param defaultValue which will be returned if no data was found
* @param isCachingEnabled `true` will enable runtime caching, to accelerate the read access of the property
*/
fun <T> sharedPreference(
sharedPreferences: SharedPreferences?,
key: String,
defaultValue: T,
isCachingEnabled: Boolean = true
): ReadWriteProperty<Any, T> = SharedPreferenceDelegate(sharedPreferences, key, defaultValue, isCachingEnabled)
/**
* Create a [ReadWriteProperty] which is saving the data into an [SharedPreferences].
* @param key for saving the data in [SharedPreferences]
* @param defaultValue which will be returned if no data was found
* @param isCachingEnabled `true` will enable runtime caching, to accelerate the read access of the property
*/
fun <T> sharedPreference(
key: String,
defaultValue: T,
isCachingEnabled: Boolean = true
): ReadWriteProperty<Any, T> = sharedPreference(sharedPreferences, key, defaultValue, isCachingEnabled)
import android.content.SharedPreferences
import androidx.preference.PreferenceManager
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
/**
* Returns [SharedPreferences] instance
*
* Note: recommended to use only in repositories and do not modify it directly,
* because it can create desynchronization with cached data
*/
val sharedPreferences: SharedPreferences?
get() = PreferenceManager.getDefaultSharedPreferences(application)
private val masterKeyAlias: String by lazy {
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
}
/**
* Returns encrypted [SharedPreferences] instance
*
* Note: recommended to use only in repositories and do not modify it directly,
* because it can create desynchronization with cached data
*/
val encryptedSharedPreferences: SharedPreferences?
get() = try {
EncryptedSharedPreferences.create(
ENCRYPTED_SHARED_PREFS_NAME,
masterKeyAlias,
application,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
} catch (e: Exception) {
Timber.d("EncryptedSharedPreferences not available", e)
null
}
private const val ENCRYPTED_SHARED_PREFS_NAME = "secret_shared_prefs"
import java.lang.ref.WeakReference
import kotlin.reflect.KProperty
inline fun <reified T> weak() = WeakReferenceDelegate<T>()
inline fun <reified T> weak(value: T) = WeakReferenceDelegate(value)
class WeakReferenceDelegate<T> {
private var weakReference: WeakReference<T>? = null
constructor()
constructor(value: T) {
weakReference = WeakReference(value)
}
operator fun getValue(thisRef: Any, property: KProperty<*>): T? = weakReference?.get()
operator fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
weakReference = WeakReference(value)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment