Last active
January 21, 2021 08:07
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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