An update to the FragmentViewBindingDelegate: the bug we’ve inherited from AutoClearedValue

Gabor Varadi
ITNEXT
Published in
3 min readJan 24, 2021

--

Jetpack Lifecycle is quite powerful. We can extract logic, and make it lifecycle-aware. In fact, this allowed us in a previous article to simplify viewbinding and make it a single line.

However, I need to provide a quick heads-up: all is not as it seems.

The provided gists and code snippets assumed that the code in AutoClearedValue is always correct. In this article, I’ll show you how to break it.

The original source

The general idea behind AutoClearedValue is that a value hidden by this delegate would only be created when it is set (in our case, when it is accessed), and when viewLifecycleOwner becomes DESTROYED, it would null out the value. Makes sense, right?

In order to access the view lifecycle, the code waits until Fragment.onCreate(), and registers the lifecycle observer there.

Then, using the fragment as the lifecycle owner (!), it attempts to register a lifecycle observer on the view lifecycle owner.

The bug hiding in plain sight

The problem is that it is possible for the view lifecycle to be initialized in such a way that fragment.onStart() and therefore viewLifecycleOwner.lifecycle.addObserver never actually happens. The fragment goes directly from onViewCreated to onDestroyView.

Meaning, fragment.onStart() and fragment.onStop() never happen.

This means that fragment.viewLifecycleOwner.observe(fragment) will NEVER be ACTIVE, and therefore the observe block will never be called. The viewLifecycle reaches DESTROYED without it being observed.

When does this happen?

If a fragment is added with commitNow(), but then is detach()ed. This would mean that this could happen with FragmentPagerAdapter, as that also uses attach/detach under the hood.

In my case, the bug surfaced when using the binding along with the following construct:

Creating a bottom navigation view with added fragments using attach/detach

In case of a bottom navigation view, where fragments were added but attached/detached, the view in the binding variable could get stuck: as it was never cleared out.

The above code should have worked out of the box, but apparently it reached a rather uncommon scenario: a fragment immediately destroying its view, without ever reaching onStart() and onStop().

How to fix it?

As the problem is caused by using the fragment as the lifecycle owner in order to observe the viewLifecycleOwnerLiveData (as the view lifecycle owner can reach DESTROYED without the fragment ever reaching STARTED), the solution is to not use the fragment as the lifecycle owner.

Using observeForever instead skips the need for the LiveData to be activated by a LifecycleOwner.

If you were using the binding, you might want to update your binding accordingly.

Conclusion

While Jetpack Lifecycle itself seems reliable and powerful, the interactions between some of its moving elements — in this case, the Fragment lifecycle and the Fragment View lifecycle — can cause surprising results.

There’s a good chance that the room for error in this regard will be reduced when the view lifecycle becomes the same as the fragment lifecycle, and there will only ever be one lifecycle to worry about — although it’s a good question as to when this will happen (Fragment 1.4.0?), and what other effects it will have (will you need addToBackStack() to keep a viewModelStore alive while navigating? How will it change the behavior of setMaxLifecycle(CREATED)?)

Either way, this is a bug that anyone who relied on the behavior of AutoClearedValue as is would have inherited themselves.

In a way, this is an interesting insight into how just like an Activity can go directly from onCreate to onDestroy, so can Fragments go directly from onViewCreated to onDestroyView.

You can check out the discussion thread on /r/androiddev.

--

--

Android dev. Zhuinden, or EpicPandaForce @ SO. Extension function fan #Kotlin, dislikes multiple Activities/Fragment backstack.