Fragment View Binding initialisation using Delegates

Sarthak Garg
Mar 27 · 4 min read
Photo by Rémi Walle on Unsplash

View Binding in Android, allows us to seamlessly interact with the user interface by providing auto-generated classes for each XML layout (on a module by module basis).

Therefore, a routine task of setting text in a TextView can be transformed from this:

val textView = findViewById<TextView>(R.id.text_view)
textView.text = "Foo"

To this:

binding.textView.text = "Foo"

Of course, view binding requires some additional boilerplate code before we can use it. In the example above, we need to first initialise the binding.

// Step 1: Declare the binding
private lateinit var binding: FragmentBuyBinding
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Step 2: Initialise it
binding = FragmentBuyBinding.inflate(layoutInflater)

// Step 3: Access it
binding.textView.text = "Foo"
}

Doing this for each fragment in our project is cumbersome. So, can we do better? Is there a cleaner, more efficient way that decreases our boilerplate code? Yes! By leveraging the power of Delegates.

The article assumes you have already set up view binding in your project. If not, refer this link to learn how to set it up.

Delegation is defined as the shifting of responsibility for some particular work from one person to another. Simply put, you can think of delegation as a process in which one object delegates (assigns/gives) a particular task to a helper object, called the delegate. It then becomes the responsibility of the delegate to execute the task and provide the result to our initial object. Kotlin inherently provides support for both class and property delegates. Since our binding object is a Fragment property, we’ll be working with property delegates.

With property delegation, our delegate is responsible for handling the get and set for the specified property. We can thus abstract our common logic behind the delegate and re-use it for similar objects which in our case is the set of classes, auto-generated by view binding.

A thorough, more descriptive explanation of delegates can be found here.

A Custom Property Delegate for Fragment View Binding

Without further ado, let’s start writing our view binding delegate.

Step 1: Create the delegate class
Create a new class FragmentViewBindingDelegate.kt and implement the ReadOnlyProperty<Fragment, T> interface. In case your property is mutable, you can use the ReadWriteProperty<Fragment, T> interface but since our binding is immutable, we only extend the read only interface. In addition, we require the binding class in our delegate constructor.

class FragmentViewBindingDelegate<T : ViewBinding>(
private val bindingClass: Class<T>
) : ReadOnlyProperty<Fragment, T> { }

Step 2: Implement the ReadOnlyProperty interface
As soon as we implement the ReadOnlyProperty interface, we are asked to implement the getValue member function.

class FragmentViewBindingDelegate<T : ViewBinding>(
private val bindingClass: Class<T>
) : ReadOnlyProperty<Fragment, T> {

override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
// TODO
}

}

The first param of getValue() is thisRef which represents the object that holds our property. It is of type Fragment in our case. The type for thisRef is governed by how we implement our interface. This is why we implemented ReadOnlyProperty<Fragment, T> instead of ReadOnlyProperty<Any?, T>.

Step 3: Implement getValue
getValue() needs to return an instance of our binding object. So we create a variable called binding. In getValue() we initialise binding if it’s not initialised and return it. This ensures we only initialise binding once and subsequently re-use the same instance.

private var binding: T? = null
override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
if (binding == null)
binding = bindingClass.getMethod("bind", View::class.java).invoke(null, thisRef.requireView()) as T
return binding!!
}

At this point, we have handled most of our boilerplate code and can initialise our binding as:

val binding by FragmentViewBindingDelegate(FragmentBuyBinding::class.java)

However, there are a few problems
1. The syntax to initialise binding can be improved
2. Our delegate is not lifecycle aware and thus, it will leak memory

Let’s resolve them. Read on.

Step 4: Clean our binding init syntax
Our delegate requires the class of our generic Class<T> to return the correct view binding instance. Another way to get this information is by using the reified keyword in Kotlin. You can learn more about reified here.

Add this inline function outside your delegate class:

inline fun <reified T : ViewBinding> Fragment.viewBinding() = FragmentViewBindingDelegate(T::class.java)

Et voilà! You can now initialise your binding with a clean, concise syntax.

private val binding by viewBinding<FragmentBuyBinding>()

Step 5: Make FragmentViewBindingDelegate lifecycle aware
To make our delegate lifecycle aware we need to pass an instance of our host fragment to it.

class FragmentViewBindingDelegate<T : ViewBinding>(
private val bindingClass: Class<T>,
private val fragment: Fragment
) : ReadOnlyProperty<Fragment, T> {

And, update our inline function.

inline fun <reified T : ViewBinding> Fragment.viewBinding() = FragmentViewBindingDelegate(T::class.java, this)

Finally, subscribe to the fragment’s lifecycle and set the binding to null once the fragment is destroyed ensuring our delegate does not leak any memory.

init {
fragment.lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onCreate(owner: LifecycleOwner) {
fragment.viewLifecycleOwnerLiveData.observe(fragment) { _viewLifecycleOwner ->
_
viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onDestroy(owner: LifecycleOwner) {
binding = null
}
})
}
}
})
}

And we’re done!

inline fun <reified T : ViewBinding> Fragment.viewBinding() = FragmentViewBindingDelegate(T::class.java, this)
class FragmentViewBindingDelegate<T : ViewBinding>(
private val bindingClass: Class<T>,
private val fragment: Fragment
) : ReadOnlyProperty<Fragment, T> {

private var binding: T? = null

init {
fragment.lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onCreate(owner: LifecycleOwner) {
fragment.viewLifecycleOwnerLiveData.observe(fragment) { viewLifecycleOwner ->
viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onDestroy(owner: LifecycleOwner) {
binding = null
}
})
}
}
})
}


override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
if (binding == null)
binding = bindingClass.getMethod("bind", View::class.java).invoke(null, thisRef.requireView()) as T
return binding!!
}

}

And in our fragment,

private val binding by viewBinding<FragmentBuyBinding>()

Connect on LinkedIn.

FloBiz Blog

India is well-poised to become a global economic superpower…

FloBiz Blog

India is well-poised to become a global economic superpower soon. In our pursuit to drive technology inclusion in small businesses, we are building FloBiz  —  the country’s first neobusiness platform.

Sarthak Garg

Written by

Senior Software Engineer (Android & Web) at FloBiz

FloBiz Blog

India is well-poised to become a global economic superpower soon. In our pursuit to drive technology inclusion in small businesses, we are building FloBiz  —  the country’s first neobusiness platform.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store