Skip to content

Instantly share code, notes, and snippets.

@Moes81
Last active January 21, 2021 08:07
Show Gist options
  • Save Moes81/6a2961109e490051805aa0bc8b0ee9c8 to your computer and use it in GitHub Desktop.
Save Moes81/6a2961109e490051805aa0bc8b0ee9c8 to your computer and use it in GitHub Desktop.
Android ViewBinding delegate function to minimize boiler plate code when working with view bindings.
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