Handling Lifecycle with View Binding in Fragments
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
}
Here 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 wait.. viewLifecycle()
isn't in AndroidX? How did we do that? By writing our own Property Delegate!
Let’s look at the full definition of viewLifecycle()
and break it down:
fun <T> Fragment.viewLifecycle(): ReadWriteProperty<Fragment, T> =
object: ReadWriteProperty<Fragment, T>, LifecycleObserver {
// A backing property to hold our value
private var binding: T? = null
init {
// Observe the View Lifecycle of the Fragment
// * See Gist for full code *
this@viewLifecycle
.viewLifecycleOwnerLiveData
.observeLifecycles()
}
@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
. A Fragment’s View can be created and destroyed many times, so it may have more than one Lifecycle. AndroidX Fragment makes it easy for us, by includingviewLifecycleOwnerLiveData
, which emits the new Lifecycle Owner when the View is recreated. - 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, which you can drop in to your project and start using!
Thanks for reading! If you liked this article make sure to 👏 it, and follow me on Twitter! If you’re a fan of Mastodon check out Mammut — the Open Source client I’m building in my spare time.