
Handling Lifecycle with View Binding in Fragments
January 15 2020
View Binding is an upcoming feature in Android, available in Android Studio 3.6 Canary 11+ which allows you to more easily interact with Views. It's quick and easy to enable, and allows for type-safe view access. We're likely all going to be using it in Fragments. Let's explore how we can use it, in a safe and easy way!
View Binding in Fragments
Let's take a look at the example from the View Binding Documentation. First we define some layout file:
result_profile.xml
<LinearLayout ... >
<TextView android:id="@+id/name" />
<ImageView android:cropToPadding="true" />
<Button android:id="@+id/button"
android:background="@drawable/rounded_button" />
</LinearLayout>
This then generates a "binding" class, ResultProfileBinding
. This class contains two fields, name
and button
, which refer to the views in our layout file. Nice!
Using this binding in a Fragment takes one more step - inflating it in onCreateView
, and returning the root view. For example:
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = ResultProfileBinding.inflate(inflater, container, false)
val view = binding.root
return view
}
On line 6 we're storing our binding class in a property so that we can access it later. As it turns out, due to the lifecycle of Fragment Views, this isn't all we have to do.
Keeping Track of Lifecycle
When our view is destroyed we need to remember to clear our property, otherwise we'll end up with a memory leak at best, and crashes at worst! The documentation recommends you do the following in your Fragments:
private var _binding: ResultProfileBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onDestroyView() {
_binding = null
}
This method works, but you can see how adding this to several different Fragment classes could get repetitive, and start to feel like boilerplate. Luckily for us, we can shorten this considerably in our Kotlin Fragments!
private var binding: ResultProfileBinding by viewLifecycle()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = ResultProfileBinding.inflate(inflater, container, false)
val view = binding.root
return view
}
We now no longer have to override onDestroyView
, and we've decreased the number of properties we have to write! But, viewLifecycle()
isn't in AndroidX? How did we do that? By writing our own Property Delegate!
Let's look at a trimmed down definition of viewLifecycle()
:
fun <T> Fragment.viewLifecycle(): ReadWriteProperty<Fragment, T> =
object: ReadWriteProperty<Fragment, T>, LifecycleObserver {
// A backing property to hold our value
private var binding: T? = null
// The current LifecycleOwner
private var viewLifecycleOwner: LifecycleOwner? = null
init {
// Observe the View LifecycleOwner, then observe the new lifecycle
this@viewLifecycle
.viewLifecycleOwnerLiveData
.observe(this@viewLifecycle, Observer { newLifecycleOwner ->
viewLifecycleOwner?.lifecycle?.removeObserver(this)
viewLifecycleOwner = newLifecycleOwner.also {
it.lifecycle.addObserver(this)
}
})
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestroy() {
// Clear out backing property just before onDestroyView
binding = null
}
override fun getValue(
thisRef: Fragment,
property: KProperty<*>
): T {
// Return the backing property if it's set
return this.binding!!
}
override fun setValue(
thisRef: Fragment,
property: KProperty<*>,
value: T
) {
// Set the backing property
this.binding = value
}
}
Wow, there's a lot going on there. Let's break it down.
viewLifecycle()
is an extension function ofFragment
, meaning we can useFragment
-related properties.viewLifecycle()
returns aReadWriteProperty<Fragment, T>
, an implementation of a property of a Fragment, which is of the generic typeT
.- We construct an anonymous class which implements
ReadWriteProperty
andLifecycleObserver
, allowing us to listen to Lifecycle Events. - In the
init
block, we observe the FragmentsviewLifecycleOwner
. This is due to the fact that the Fragments view may go through multiple different lifecycles, being destroyed and re-created multiple times. - Finally, when the View's Lifecycle Owner changes, we observe the new Lifecycle. On the
ON_DESTROY
event sent whenonDestroyView
is about to be called, we null out our backing property.
This gives us the same behaviour as in the View Binding Documentation, but with much less code to cart around in our Fragments! You can find a full, more flexible example in this Gist. For more information around Kotlin Delegated Properties, have a read of my "Handing the Reins to Kotlin Delegates" blog series!