Skip to content

Instantly share code, notes, and snippets.

@Moes81
Last active December 29, 2021 16:23
Show Gist options
  • Save Moes81/85c165b6e828342e07dcfaa7ce660886 to your computer and use it in GitHub Desktop.
Save Moes81/85c165b6e828342e07dcfaa7ce660886 to your computer and use it in GitHub Desktop.
Some useful delegates
package delegates
import java.lang.ref.SoftReference
import java.lang.ref.WeakReference
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
// Idea taken from: https://medium.com/@pgopinath/idiomatic-weakreference-implementation-by-delegate-in-kotlin-65ec60287de6
fun <T>weakReference(tIn: T? = null): ReadWriteProperty<Any?, T?> {
return object : ReadWriteProperty<Any?, T?> {
var t = WeakReference<T?>(tIn)
override fun getValue(thisRef: Any?, property: KProperty<*>): T? {
return t.get()
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
t = WeakReference(value)
}
}
}
fun <T>softReference(tIn: T? = null): ReadWriteProperty<Any?, T?> {
return object : ReadWriteProperty<Any?, T?> {
var t = SoftReference<T?>(tIn)
override fun getValue(thisRef: Any?, property: KProperty<*>): T? {
return t.get()
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
t = SoftReference(value)
}
}
}
class Employee(val name: String, val id: String)
fun main() {
// This example may not work a expected, mostly because System.gc() need not trigger garbage collection.
// It is just a directive to the JVM but it need not trigger GC.
// There is no other way to simulate gc call other than triggering gc in a loop
run {
var employeeWeak: Employee? by weakReference(null)
employeeWeak = Employee("alok", "sjdfsdkjfdl")
println(employeeWeak?.name)
System.gc() // This may not trigger but it happens in JVM long running processes as needed.
println(employeeWeak?.name) // May be null if gc has been performed by the JVM.
}
run {
// Remember, soft reference are cleared only when there is a clear memory shortage
var employeeWeak: Employee? by softReference(Employee("gecko", "sjdfsdkjfd11l"))
println(employeeWeak?.name)
System.gc() // This may not trigger but it happens in JVM long running processes as needed.
println(employeeWeak?.name) // Need not be null. Mostly non-null
}
}
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.viewbinding.ViewBinding
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
//
// Using these delegates requires `androidx.lifecycle:lifecycle-common-java8` as dependency. This dependency includes
// the [DefaultLifecycleObserver] used in this file.
// Idea taken from: https://gist.github.com/gmk57/aefa53e9736d4d4fb2284596fb62710d
//
/**
* Activity binding delegate, may be used since onCreate up to onDestroy (inclusive).
*
* Usage:
* ```
* class MainActivity : AppCompatActivity() {
* private val binding by viewBinding(ActivityMainBinding::inflate)
*
* override fun onCreate(savedInstanceState: Bundle?) {
* super.onCreate(savedInstanceState)
* setContentView(binding.root)
* binding.button.text = "Bound!"
* }
* }
* ```
*
* */
inline fun <T : ViewBinding> AppCompatActivity.viewBinding(crossinline factory: (LayoutInflater) -> T) =
lazy(LazyThreadSafetyMode.NONE) {
factory(layoutInflater)
}
/**
* Fragment binding delegate, may be used since onViewCreated up to onDestroyView (inclusive)
*
* Usage:
* ```
* // Don't forget to pass layoutId in Fragment constructor
* class RegularFragment : Fragment(R.layout.fragment_regular) {
* private val binding by viewBinding(FragmentRegularBinding::bind)
*
* override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
* super.onViewCreated(view, savedInstanceState)
* binding.button.text = "Bound!"
* }
* }
* ```
*
*/
fun <T : ViewBinding> Fragment.viewBinding(factory: (View) -> T): ReadOnlyProperty<Fragment, T> =
object : ReadOnlyProperty<Fragment, T>, DefaultLifecycleObserver {
private var binding: T? = null
override fun getValue(thisRef: Fragment, property: KProperty<*>): T =
binding ?: factory(requireView()).also {
// if binding is accessed after Lifecycle is DESTROYED, create new instance, but don't cache it
if (viewLifecycleOwner.lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) {
viewLifecycleOwner.lifecycle.addObserver(this)
binding = it
}
}
override fun onDestroy(owner: LifecycleOwner) {
binding = null
}
}
/**
* [DialogFragment] binding delegate, may be used since onCreateView/onCreateDialog up to onDestroy (inclusive).
*
* Usage with [DialogFragment.onCreateView]:
* ```
* private val binding by viewBinding(MyDialogFragmentBinding::inflate)
*
* override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
* return viewBinding.root
* }
* ```
*
* Usage with [DialogFragment.onCreateDialog]:
* ```
* private val binding by viewBinding(MyDialogFragmentBinding::inflate)
*
* override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
* binding.button.text = "Bound!"
* return AlertDialog.Builder(requireContext()).setView(binding.root).create()
* }
*
* ```
*/
inline fun <T : ViewBinding> DialogFragment.viewBinding(crossinline factory: (LayoutInflater) -> T) =
lazy(LazyThreadSafetyMode.NONE) {
factory(layoutInflater)
}
/**
* Not really a delegate, just a small helper for RecyclerView.ViewHolders.
*
* Prefer to encapsulate view creation & manipulation inside `ViewHolder`.
* In this case `BoundHolder` can be used as a superclass.
*
* Usage:
* ```
* class Adapter3 : ListAdapter<String, Adapter3.Holder>(Differ()) {
* override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = Holder(parent)
*
* override fun onBindViewHolder(holder: Holder, position: Int) = holder.bind(getItem(position))
*
* class Holder(parent: ViewGroup) : BoundHolder<ListItemBinding>(parent.viewBinding(ListItemBinding::inflate)) {
* fun bind(item: String) {
* binding.textView.text = item
* }
* }
*
* private class Differ : DiffUtil.ItemCallback<String>() { ... }
* }
*
* abstract class BoundHolder<T : ViewBinding>(protected val binding: T) : RecyclerView.ViewHolder(binding.root)
* ```
*/
inline fun <T : ViewBinding> ViewGroup.viewBinding(factory: (LayoutInflater, ViewGroup, Boolean) -> T) =
factory(LayoutInflater.from(context), this, false)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment