Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Fragment view binding delegate
// https://github.com/Zhuinden/fragmentviewbindingdelegate-kt
import android.view.View
import androidx.fragment.app.Fragment
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.Observer
import androidx.viewbinding.ViewBinding
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
class FragmentViewBindingDelegate<T : ViewBinding>(
val fragment: Fragment,
val viewBindingFactory: (View) -> T
) : ReadOnlyProperty<Fragment, T> {
private var binding: T? = null
init {
fragment.lifecycle.addObserver(object : DefaultLifecycleObserver {
val viewLifecycleOwnerLiveDataObserver =
Observer<LifecycleOwner?> {
val viewLifecycleOwner = it ?: return@Observer
viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onDestroy(owner: LifecycleOwner) {
binding = null
}
})
}
override fun onCreate(owner: LifecycleOwner) {
fragment.viewLifecycleOwnerLiveData.observeForever(viewLifecycleOwnerLiveDataObserver)
}
override fun onDestroy(owner: LifecycleOwner) {
fragment.viewLifecycleOwnerLiveData.removeObserver(viewLifecycleOwnerLiveDataObserver)
}
})
}
override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
val binding = binding
if (binding != null) {
return binding
}
val lifecycle = fragment.viewLifecycleOwner.lifecycle
if (!lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) {
throw IllegalStateException("Should not attempt to get bindings when Fragment views are destroyed.")
}
return viewBindingFactory(thisRef.requireView()).also { this.binding = it }
}
}
fun <T : ViewBinding> Fragment.viewBinding(viewBindingFactory: (View) -> T) =
FragmentViewBindingDelegate(this, viewBindingFactory)
@matthewbahr

This comment has been minimized.

Copy link

@matthewbahr matthewbahr commented May 19, 2020

Edit: Imports are now shown, this is unnecessary.

If anyone is having a hard time with line 11 showing the error
Type mismatch: inferred type is (Nothing) -> [ERROR : Cannot infer type variable TypeVariable(_L)] but Observer<in LifecycleOwner!> was expected

You need to import:

import androidx.lifecycle.observe

Android Studio doesn't know what to import there with Alt+Enter

@marlonlom

This comment has been minimized.

Copy link

@marlonlom marlonlom commented May 25, 2020

@Zhuinden Please put the complete imports for this gist.

@Zhuinden

This comment has been minimized.

Copy link
Owner Author

@Zhuinden Zhuinden commented May 25, 2020

@marlonlom done

@MadRatSRP

This comment has been minimized.

Copy link

@MadRatSRP MadRatSRP commented Jul 21, 2020

 Caused by: java.lang.IllegalStateException: Fragment ImportFragment{1957879} (65eb5cfc-8500-4dc7-b7b5-8f060c772ace) id=0x7f0800cc} did not return a View from onCreateView() or this was called before onCreateView().

Did anyone had this kind of error?

Here is my onCreateView code:

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
return binding.root
}

@Zhuinden

This comment has been minimized.

Copy link
Owner Author

@Zhuinden Zhuinden commented Jul 22, 2020

The sample above relies on Fragment(R.layout.blah) without the use of overriding onCreateView.

@MadRatSRP

This comment has been minimized.

Copy link

@MadRatSRP MadRatSRP commented Jul 22, 2020

The sample above relies on Fragment(R.layout.blah) without the use of overriding onCreateView.

Okay, could you please tell me what should i do if i want to use onCreate and onCreateView with your code?

@Zhuinden

This comment has been minimized.

Copy link
Owner Author

@Zhuinden Zhuinden commented Jul 22, 2020

Stop using onCreateView and use onViewCreated instead

OnCreate is unaffected

@matthewbahr

This comment has been minimized.

Copy link

@matthewbahr matthewbahr commented Jul 22, 2020

@MadRatSRP I can also confirm that onViewCreated works well. I use it regularly for View Lifecycle Scoped elements of a fragment (OnClickListeners etc) without any issues.

@marlonlom

This comment has been minimized.

Copy link

@marlonlom marlonlom commented Jul 23, 2020

@MadRatSRP I can also confirm that onViewCreated works well. I use it regularly for View Lifecycle Scoped elements of a fragment (OnClickListeners etc) without any issues.

Thats nice... +1 for that!

@marlonlom

This comment has been minimized.

Copy link

@marlonlom marlonlom commented Jul 23, 2020

@Zhuinden ... about the usage, its like its required to add as parameter the Fragment class to return the generated ViewBinding class object, am i right?

befire adding this gist, i was using it like this:

private val viewBinding: FragmentMainBinding by viewBinding()
@Zhuinden

This comment has been minimized.

Copy link
Owner Author

@Zhuinden Zhuinden commented Jul 24, 2020

All your questions are answered in the article I wrote this gist for “Simple one-liner ViewBinding in Fragments and Activities with Kotlin” by Gabor Varadi https://link.medium.com/tlHuZo7xn8

@jairrab

This comment has been minimized.

Copy link

@jairrab jairrab commented Sep 16, 2020

Was there any reason you chose to use this:

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
                        }
                    })
                }
            }
        })
    }

instead of the simpler?

init {
        fragment.lifecycle.addObserver(object : DefaultLifecycleObserver {
            override fun onDestroy(owner: LifecycleOwner) {
                _binding = null
            }
        })
    }
@Zhuinden

This comment has been minimized.

Copy link
Owner Author

@Zhuinden Zhuinden commented Sep 16, 2020

The Fragment lifecycle would only destroy the binding in onDestroy, but not in onDestroyView.

@jairrab

This comment has been minimized.

Copy link

@jairrab jairrab commented Sep 16, 2020

Got it. Thanks, forgot that fragment.viewLifecycleOwner.lifecycle was intended for that.

@bohsen

This comment has been minimized.

Copy link

@bohsen bohsen commented Oct 5, 2020

Thanks for sharing. Today I used your gist as inspiration to create a binding delegate for use in custom views, using relatively new ViewTreeLifecycleOwner:

class CustomViewBindingDelegate<out T : ViewBinding>(
        val view: View,
        val viewBindingFactory: (LayoutInflater, ViewGroup) -> T
) : ReadOnlyProperty<View, T> {

    private var binding: T? = null

    init {
        view.doOnAttach {
            Timber.d("Adding lifecycleobserver")
            val lifecycleOwner = checkNotNull(view.findViewTreeLifecycleOwner()) {
                "View: $view does not appear to have a lifecycleOwner"
            }
            lifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
                override fun onDestroy(owner: LifecycleOwner) {
                    super.onDestroy(owner)
                    Timber.d("Calling onDestroy")
                    binding = null
                }
            })
            Timber.d("Lifecycleobserver added") }
    }

    override fun getValue(thisRef: View, property: KProperty<*>): T {
        Timber.d("Binding $thisRef")
        val binding = binding
        if (binding != null) {
            return binding
        }

        return viewBindingFactory(
            LayoutInflater.from(thisRef.context),
            thisRef as ViewGroup).also { this@CustomViewBindingDelegate.binding = it }
    }
}

fun <T : ViewBinding> View.viewBinding(viewBindingFactory: (LayoutInflater, ViewGroup) -> T) =
    CustomViewBindingDelegate(this, viewBindingFactory)

Seems to work.
Usage:

class SampleView @JvmOverloads constructor(context: Context, attrs: AttributeSet?, @AttrRes defStyleAttr: Int) 
: ConstraintLayout(context, attrs, defStyleAttr) {
    ...
    private val binding: LayoutSampleViewBinding by viewBinding(LayoutSampleViewBinding::inflate)
    ...
}
@nimdokai

This comment has been minimized.

Copy link

@nimdokai nimdokai commented Nov 3, 2020

The Fragment lifecycle would only destroy the binding in onDestroy, but not in onDestroyView.

Could explain why the below approach is not valid as well?

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

This comment has been minimized.

Copy link
Owner Author

@Zhuinden Zhuinden commented Nov 3, 2020

Last time I tried, the viewLifecycleOwnerLiveData wasn't accessible before onCreate, although I just directly copied the latest variant of AutoClearedValue in the architecture-components-samples as the foundation of this delegate.

@nimdokai

This comment has been minimized.

Copy link

@nimdokai nimdokai commented Nov 3, 2020

I've tried both versions, and I couldn't spot the difference. In both cases binding is accessible inside onViewCreated().

Regarding the viewLifecycleOwnerLiveData for androix.fragment 1.2.5 there is annotation:

// This is initialized in performCreateView and unavailable outside of the
// onCreateView/onDestroyView lifecycle
@Nullable FragmentViewLifecycleOwner mViewLifecycleOwner;
MutableLiveData<LifecycleOwner> mViewLifecycleOwnerLiveData = new MutableLiveData<>();
@micer

This comment has been minimized.

Copy link

@micer micer commented Nov 10, 2020

The sample above relies on Fragment(R.layout.blah) without the use of overriding onCreateView.

Okay, could you please tell me what should i do if i want to use onCreate and onCreateView with your code?

Hey, do you have any idea how to do this in DialogFragment()? The one from androidx.fragment:fragment:1.1.0 doesn't have layout resource id in constructor.

@ealfonso93

This comment has been minimized.

Copy link

@ealfonso93 ealfonso93 commented Nov 18, 2020

The sample above relies on Fragment(R.layout.blah) without the use of overriding onCreateView.

Okay, could you please tell me what should i do if i want to use onCreate and onCreateView with your code?

Hey, do you have any idea how to do this in DialogFragment()? The one from androidx.fragment:fragment:1.1.0 doesn't have layout resource id in constructor.

You should be able to use the onCreateView from the DialogFragment to return inflater.inflate(R.layout.foo, container, false) and then use the binding with the delegate like private val binding by viewBinding(FooBinding::bind).

The mistake is trying to have the binding before the actual onCreateView happened.

@AndroidDeveloperLB

This comment has been minimized.

Copy link

@AndroidDeveloperLB AndroidDeveloperLB commented Nov 21, 2020

I get this warning about fragment.viewLifecycleOwnerLiveData.observe :

Candidate resolution will be changed soon, please use fully qualified name to invoke the following closer candidate explicitly:

What does it mean? What should I use instead?

@ablenesi

This comment has been minimized.

Copy link

@ablenesi ablenesi commented Nov 30, 2020

That import can now be deleted, in latest Kotlin (1.4+) version is supported by default.
For more details see: SAM conversions for Kotlin interfaces

@AndroidDeveloperLB

This comment has been minimized.

Copy link

@AndroidDeveloperLB AndroidDeveloperLB commented Nov 30, 2020

@ablenesi

That import can now be deleted, in latest Kotlin (1.4+) version is supported by default.
For more details see: SAM conversions for Kotlin interfaces

To which reply did you write this?

@ealfonso93

This comment has been minimized.

Copy link

@ealfonso93 ealfonso93 commented Nov 30, 2020

@ablenesi

That import can now be deleted, in latest Kotlin (1.4+) version is supported by default.
For more details see: SAM conversions for Kotlin interfaces

To which reply did you write this?

It's meant to you @AndroidDeveloperLB you have to check the file where this warning is and remove the import importing .observe

@AndroidDeveloperLB

This comment has been minimized.

Copy link

@AndroidDeveloperLB AndroidDeveloperLB commented Nov 30, 2020

@ablenesi

Oh I see. You mean this:

import androidx.lifecycle.observe

Sorry. Thank you!

@jishincreo

This comment has been minimized.

Copy link

@jishincreo jishincreo commented Jan 5, 2021

I'm seeing memory leaks from the root of the fragment when using this. The error says onDestroyView was called but the binding was holding the activity instance. Any ideas?

@Zhuinden

This comment has been minimized.

Copy link
Owner Author

@Zhuinden Zhuinden commented Jan 5, 2021

How did you get an Activity instance into the binding inside a Fragment? 🤔

@RohitSurwase

This comment has been minimized.

Copy link

@RohitSurwase RohitSurwase commented Jan 11, 2021

There are two popular articles on View Binding Delegates, 1st is https://proandroiddev.com/make-android-view-binding-great-with-kotlin-b71dd9c87719 and 2nd is yours. I have seen both implementation, there are some differences in the implementation. Being a developer of one such solution, you would be able to understand the difference better. So, how does you implementation differ from this https://github.com/kirich1409/ViewBindingPropertyDelegate implementation? Also, I have have seen some comments which warn against using such implementation. So what are those things that anyone should keep in mind while using such implementation? I hope you would shed some light on this. Thanks

@Zhuinden

This comment has been minimized.

Copy link
Owner Author

@Zhuinden Zhuinden commented Jan 11, 2021

At the time of writing the article which is about a year ago, these libraries didn't exist... You can use them, but I wouldn't use a variant that uses reflection.

@RohitSurwase

This comment has been minimized.

Copy link

@RohitSurwase RohitSurwase commented Jan 11, 2021

I just asked similar question on the another solution I mentioned. As, we can grow better as a community. Also, I think many others may have similar doubt and this would be helpful for all of us. Thanks!

@RohitSurwase

This comment has been minimized.

Copy link

@RohitSurwase RohitSurwase commented Jan 11, 2021

Yes, even I would not use variant with reflection but there are some differences in the implementation which does not use reflection. Would you shed some light on that? Thanks for replying.

@Zhuinden

This comment has been minimized.

Copy link
Owner Author

@Zhuinden Zhuinden commented Jan 12, 2021

The library is less restrictive as they only clear manually rather than in ON_DESTROY which I think is technically leaky, and they defer the actual clear to the next loop for some reason. It's probably so that you can remove adapters from RecyclerView but you can do that from onViewCreated with a lifecycle event listener anyway.

The snippet you see here is pretty much self-contained as is.

@RohitSurwase

This comment has been minimized.

Copy link

@RohitSurwase RohitSurwase commented Jan 13, 2021

Yes, this snippet is self-contained and self-explanatory as is. Thanks for sharing your views on the another solution after reviewing. This is really useful. Thanks again!

@Zhuinden

This comment has been minimized.

Copy link
Owner Author

@Zhuinden Zhuinden commented Jan 19, 2021

I had to fix a really silly bug in the behavior of viewLifecycleOwner, that it is possible to get onCreateView called, and then onCreateView called AGAIN without onDestroyView being called.

if (binding != null && thisRef.view === binding.root) {

I wonder if this is capable of, uh, causing memory leaks. This is a Fragment lifecycle bug, really. 🤔

@gmk57

This comment has been minimized.

Copy link

@gmk57 gmk57 commented Jan 22, 2021

This is a Fragment lifecycle bug, really.

@Zhuinden This is rather scary. :( Many apps may rely on onCreateView/onDestroyView symmetry. Duplicate subscriptions to LiveData and whatnot. Can it be reproduced? Is it reported to Google?

they only clear manually rather than in ON_DESTROY which I think is technically leaky, and they defer the actual clear to the next loop for some reason

AFAIK, solution by Kirill Rozov (I only looked at non-reflective one) clears in DefaultLifecycleObserver.onDestroy too, but with slight delay to allow accessing binding from Fragment.onDestroyView, which unfortunately causes a bug in edge case.

@RohitSurwase You can also take a look at my variant, which is essentially a hybrid of this solution (external API is the same) and that one (LifecycleObserver is registered on first access, which simplifies logic a bit).

@RohitSurwase

This comment has been minimized.

Copy link

@RohitSurwase RohitSurwase commented Jan 23, 2021

@gmk57 Sure, I will have a look into it. Thanks!

@Zhuinden

This comment has been minimized.

Copy link
Owner Author

@Zhuinden Zhuinden commented Jan 23, 2021

@gmk57 It was... uh, not a Fragment lifecycle bug.

So the article at https://medium.com/@Zhuinden/simple-one-liner-viewbinding-in-fragments-and-activities-with-kotlin-961430c6c07c explicitly says:

Once we know that and we’ve read the source code for AutoClearedValue, it’s quite easy to set up in Fragments.

We have definitely read the source code, but I for one didn't notice a pesky bug in it:

class AutoClearedValue<T : Any>(val fragment: Fragment) : ReadWriteProperty<Fragment, T> {
    private var _value: T? = null

    init {
        fragment.lifecycle.addObserver(object: DefaultLifecycleObserver {
            override fun onCreate(owner: LifecycleOwner) {
                fragment.viewLifecycleOwnerLiveData.observe(fragment) { viewLifecycleOwner -> // <-- OBSERVE WITH FRAGMENT LIFECYCLE
                    viewLifecycleOwner?.lifecycle?.addObserver(object: DefaultLifecycleObserver { // <-- CALLED ONLY WHEN FRAGMENT LIFECYCLE IS ACTIVE
                        override fun onDestroy(owner: LifecycleOwner) {
                            _value = null

If your Fragment is added but detached, then viewLifecycleOwner goes directly from CREATED to DESTROYED, while the Fragment didn't actually go to STARTED. Therefore, the observe block is NEVER called, because the LiveData never actually becomes active!

The viewLifecycleOwner never gets the lifecycle observer registered, and the binding is never actually cleared.

I have updated the snippet accordingly, but I think at this point, this requires a new full-fledged article so people know about the bug. 👀

Conclusion: onDestroyView was called, the observer could however never receive it. This bug was directly inherited from AutoClearedValue, and nobody has actually noticed it for the past 1 year 2 weeks. 🤔

@RohitSurwase

This comment has been minimized.

Copy link

@RohitSurwase RohitSurwase commented Jan 23, 2021

@Zhuinden That's a very good catch. Thanks for sharing the details and updating the gist.

@erikhuizinga

This comment has been minimized.

Copy link

@erikhuizinga erikhuizinga commented Jan 25, 2021

this requires a new full-fledged article so people know about the bug.

@Zhuinden let us know here about that article too, if you every publish it.

@simonschiller

This comment has been minimized.

Copy link

@simonschiller simonschiller commented Jan 28, 2021

I think there is a small issue in this check:

val lifecycle = fragment.viewLifecycleOwner.lifecycle
if (!lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) {
    throw IllegalStateException("Should not attempt to get bindings when Fragment views are destroyed.")
}

When onCreateView hasn't been called yet, getViewLifecycleOwner throws an exception, so we never enter this block. See here: https://github.com/androidx/androidx/blob/a045470a0153d884c9609edac1127f6e4e794978/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java#L359-L365

Checking for fragment.view == null should work instead. Only a cosmetic issue though, as you end up with an IllegalStateException either way.

@gmk57

This comment has been minimized.

Copy link

@gmk57 gmk57 commented Jan 29, 2021

@simonschiller Removing the check for lifecycle.currentState will lead to bug if binding is accessed from onDestroyView().

@kosaanta

This comment has been minimized.

Copy link

@kosaanta kosaanta commented Feb 4, 2021

@Zhuinden I have a question about fragments. How about when you want to clear the data of a view in order to avoid leaks. Before I was clearing that in onDestroyView now when onDestroyView is called the binding is null. Do you have any ideas? Is there any point to clear the data of the view now that the binding gets nulled out onDestroy (wont that clear all the views of the fragments), I could get memory leaks after that?

@ealfonso93

This comment has been minimized.

Copy link

@ealfonso93 ealfonso93 commented Feb 4, 2021

@kosaanta I added a closure to execute when the onDestroy is called so on it will let yo do the cleanup, it passes the binding so you do whatever you need on your fragment related to views:

class FragmentViewBindingDelegate<T : ViewBinding>(
    val fragment: Fragment,
    val viewBindingFactory: (View) -> T,
    val destroyTask: ((T) -> Unit)? = null
)
override fun onDestroy(owner: LifecycleOwner) {
                            destroyTask?.invoke(requireNotNull(binding))
                            binding = null
                        }

Edit: Added requireNotNull when invoking destroyTask, see: https://gist.github.com/Zhuinden/ea3189198938bd16c03db628e084a4fa#gistcomment-3622614

@Zhuinden

This comment has been minimized.

Copy link
Owner Author

@Zhuinden Zhuinden commented Feb 4, 2021

@kosaanta either the approach above, or you can register a DefaultLifecycleObserver in onViewCreated and wait for onDestroy of the viewLifecycleOwner's lifecycle.

If you did val binding = binding in onViewCreated then it'll work

@kosaanta

This comment has been minimized.

Copy link

@kosaanta kosaanta commented Feb 4, 2021

Thank you guys! @ealfonso93 and @Zhuinden

@Drjacky

This comment has been minimized.

Copy link

@Drjacky Drjacky commented Feb 6, 2021

@ealfonso93
If I'm not mistaken, val destroyTask: ((T?) -> Unit)? actually.
binding is nullable.

@ealfonso93

This comment has been minimized.

Copy link

@ealfonso93 ealfonso93 commented Feb 8, 2021

@ealfonso93
If I'm not mistaken, val destroyTask: ((T?) -> Unit)? actually.
binding is nullable.

It is, sorry I didn't specify this, but you have to make sure that when you pass it to the destroy task is not null, in my case I use requireNotNull from the kotlin std, see below how it'd look like:

destroyTask?.invoke(requireNotNull(binding))
@Zhuinden

This comment has been minimized.

Copy link
Owner Author

@Zhuinden Zhuinden commented Feb 10, 2021

Due to popular request, the gist is now out as a library.

    repositories {
        maven { url "https://jitpack.io" }  
    }

    implementation 'com.github.Zhuinden:fragmentviewbindingdelegate-kt:1.0.0'

Is it late? Maybe, but at least people can stop copy-pasting it now 😂

@ziselos

This comment has been minimized.

Copy link

@ziselos ziselos commented Feb 11, 2021

I replace the way i used for binding views with FragmentViewBindingDelegate and everything worked great until i came across with the below scenario:
I have a parent fragment with a tabLayout of 4 tabs (inner fragments). If i click 4th tab and then press back(it should go back to the first tab), on onBackPressed handling on root activity i am getting

java.lang.IllegalStateException: Can't access the Fragment View's LifecycleOwner when getView() is null i.e., before onCreateView() or after onDestroyView()

If i understand correctly this is because i am trying to access views from 1st fragment (in order to set viewpager current position to 0) but these views no longer exists as the 1st tab fragment has been destroyed. The only way to make it work for now is to recreate the parent fragment.

@costular

This comment has been minimized.

Copy link

@costular costular commented Feb 16, 2021

I replace the way i used for binding views with FragmentViewBindingDelegate and everything worked great until i came across with the below scenario:
I have a parent fragment with a tabLayout of 4 tabs (inner fragments). If i click 4th tab and then press back(it should go back to the first tab), on onBackPressed handling on root activity i am getting

java.lang.IllegalStateException: Can't access the Fragment View's LifecycleOwner when getView() is null i.e., before onCreateView() or after onDestroyView()

If i understand correctly this is because i am trying to access views from 1st fragment (in order to set viewpager current position to 0) but these views no longer exists as the 1st tab fragment has been destroyed. The only way to make it work for now is to recreate the parent fragment.

Reporting the same issue that @ziselos described

@Zhuinden

This comment has been minimized.

Copy link
Owner Author

@Zhuinden Zhuinden commented Feb 16, 2021

I would need an actual project to debug the timing of things, and to see if val binding = binding is called at the start of onViewCreated

@ziselos

This comment has been minimized.

Copy link

@ziselos ziselos commented Feb 17, 2021

@Zhuinden
I just send you an email with access on project repo that reproduce the crash

@Zhuinden

This comment has been minimized.

Copy link
Owner Author

@Zhuinden Zhuinden commented Feb 17, 2021

I could not reproduce the crash, but ViewPager's FragmentPagerAdapter/FragmentStatePagerAdapter should NEVER contain a List<Fragment>. And you should NEVER initialize a Fragment with new SomeFragment() WITHOUT first checking for findFragmentByTag if that given fragment exists.

https://stackoverflow.com/questions/54279509/how-to-get-elements-of-fragments-created-by-viewpager-in-mainactivity

So that could easily be a cause of your problem.

@JBlaz

This comment has been minimized.

Copy link

@JBlaz JBlaz commented Feb 22, 2021

@Zhuinden

throw IllegalStateException("Should not attempt to get bindings when Fragment views are destroyed.")

The string in this exception seems a little misleading since if you access a binding in onDestoryView() it crashes but in onDestoryView() getView() is not null. This comment implies that you should be able to safely do this

getView()?.let {
    binding.map.onDestroy()
}

But this causes that crash when called from onDestroyView() as has been mentioned by others.

@hvsimon

This comment has been minimized.

Copy link

@hvsimon hvsimon commented Feb 25, 2021

@Zhuinden

What if I need to assess binding to call some view release method when onDestoryView(). How can I do?

For example

fun onDestoryView() {
    // Below code will throw IllegalStateException("Should not attempt to get bindings when Fragment views are destroyed.")
    binding.recyclerView.adapter = null
    binding.myView.onDestroy()
    super.onDestroyView()
}
@Drjacky

This comment has been minimized.

Copy link

@Drjacky Drjacky commented Feb 25, 2021

What if I need to assess binding to call some view release method when onDestoryView(). How can I do?

For example

fun onDestoryView() {
    // Below code will throw IllegalStateException("Should not attempt to get bindings when Fragment views are destroyed.")
    binding.recyclerView.adapter = null
    binding.myView.onDestroy()
    super.onDestroyView()
}

@hvsimon

https://gist.github.com/Zhuinden/ea3189198938bd16c03db628e084a4fa#gistcomment-3619653
https://gist.github.com/Zhuinden/ea3189198938bd16c03db628e084a4fa#gistcomment-3622614

@gmk57

This comment has been minimized.

Copy link

@gmk57 gmk57 commented Feb 25, 2021

What if I need to assess binding to call some view release method when onDestoryView()

You can also use this or this approach.

@Cochi

This comment has been minimized.

Copy link

@Cochi Cochi commented Apr 8, 2021

I replace the way i used for binding views with FragmentViewBindingDelegate and everything worked great until i came across with the below scenario:
I have a parent fragment with a tabLayout of 4 tabs (inner fragments). If i click 4th tab and then press back(it should go back to the first tab), on onBackPressed handling on root activity i am getting

java.lang.IllegalStateException: Can't access the Fragment View's LifecycleOwner when getView() is null i.e., before onCreateView() or after onDestroyView()

If i understand correctly this is because i am trying to access views from 1st fragment (in order to set viewpager current position to 0) but these views no longer exists as the 1st tab fragment has been destroyed. The only way to make it work for now is to recreate the parent fragment.

Same issue that @ziselos

I decided to make the viewBinding nullable and add a null safety check on my binding object binding?.run{...}:

class CustomViewBindingDelegate<out T : ViewBinding>(
    val view: View,
    val viewBindingFactory: (LayoutInflater, ViewGroup) -> T
) : ReadOnlyProperty<View, T?> {
   ...
}



override fun getValue(thisRef: Fragment, property: KProperty<*>): T? {
    val binding = binding
    if (binding != null) {
        return binding
    }

     try {
         val lifecycle = fragment.viewLifecycleOwner.lifecycle
         if (!lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) {
            throw IllegalStateException("Should not attempt to get bindings when Fragment views are destroyed.")
         }
    } catch (e : Exception) {
        Timber.e(e)
    }
     
    return thisRef.view?.let { viewBindingFactory(it).also { this.binding = it }}
}
@Gryzor

This comment has been minimized.

Copy link

@Gryzor Gryzor commented Apr 12, 2021

This "Fancy" one-liner is simply replacing this code:

private var _binding: SomeBinding?
private val binding = _binding!!
onDestroyView() {
   _binding = null
}
onCreateView() {
    _binding = SomeBinding.inflate(...)
    return _binding.root
}

Another bad news about the "delegate":

If you open your screen, all is nice and shinny, and then you go to settings -> accessibility and change the font size, DefaultLifecycleObserver does not trigger appropriately. So your "binding" is now outdated and you can see what the leak (and side-effect) is.

The reason is buried somewhere in Android, but long story short, is the fragment is not really destroyed, but its view is.

@gmk57

This comment has been minimized.

Copy link

@gmk57 gmk57 commented Apr 12, 2021

This "Fancy" one-liner is simply replacing this code:

Exactly, because we don't want this ugly and error-prone code repeated all over our fragments.

DefaultLifecycleObserver does not trigger appropriately

It is triggered, if you attach it to a correct lifecycle (viewLifecycleOwner.lifecycle).

the fragment is not really destroyed, but its view is

Yes, because fragments have two separate lifecycles. The same happens when you put a fragment in a back stack.

@Zhuinden

This comment has been minimized.

Copy link
Owner Author

@Zhuinden Zhuinden commented Apr 12, 2021

This "Fancy" one-liner is simply replacing this code:

private var _binding: SomeBinding?
private val binding = _binding!!
onDestroyView() {
   _binding = null
}
onCreateView() {
    _binding = SomeBinding.inflate(...)
    return _binding.root
}

Another bad news about the "delegate":

If you open your screen, all is nice and shinny, and then you go to settings -> accessibility and change the font size, DefaultLifecycleObserver does not trigger appropriately. So your "binding" is now outdated and you can see what the leak (and side-effect) is.

The reason is buried somewhere in Android, but long story short, is the fragment is not really destroyed, but its view is.

Interesting, I would expect that if Android kills the view, then onDestroyView should be called. 🤔 I might have to look at it later, although I would expect the lifecycle to be symmetric...

@Gryzor

This comment has been minimized.

Copy link

@Gryzor Gryzor commented Apr 13, 2021

Interesting, I would expect that if Android kills the view, then onDestroyView should be called. 🤔 I might have to look at it later, although I would expect the lifecycle to be symmetric...

We did too, but I found out they were using setRetainInstance(true) and this alters the lifecycle of a Fragment in Fragment Manager during these configuration changes; this confuses DefaultLifecycleObserver's owner and the callback is not called.

Exactly, because we don't want this ugly and error-prone code repeated all over our fragments.

I don't think this is "too error prone", but then again, I don't know your coding practices, style, and the environment you have to work on. Not all teams work the same way, so I understand the motivation behind something like this.

It is triggered, if you attach it to a correct lifecycle (viewLifecycleOwner.lifecycle).

It is not triggered if a fragment has `setRetainInstance(true). This may or may not surprise you, but a lot of apps, companies, devs, etc. enabled that in the past (maybe still do) because it's a "cheap way to get the state saved". It's a thing, and while I've never personally used it, I've faced many places where the side-effects of that are everywhere.

This is an unexpected side-effect. In a mid-large size project, it's very easy for things like this to slip through the cracks.

Yes, because fragments have two separate lifecycles. The same happens when you put a fragment in a back stack.

Thank you, I've been doing Android since 2010, I kind of knew about this; I was there when What The Fragment happened... :/

@Zhuinden

This comment has been minimized.

Copy link
Owner Author

@Zhuinden Zhuinden commented Apr 13, 2021

setRetainFragment() is technically deprecated, but I admit I didn't think about that. 🤔

At that case, that just means the AutoClearedValue I based this code on is conceptually flawed.

@Gryzor

This comment has been minimized.

Copy link

@Gryzor Gryzor commented Apr 13, 2021

setRetainFragment() is technically deprecated, but I admit I didn't think about that. thinking

Agreed, me neither. It took me a few days to make the link. The project I am working on, started using ViewBinding, and they made a "clever" property delegate like this... and during a regression test, someone from the QA dept. detected that after going to accessibility and changing the font size, the screens became unresponsive (not ANR style, but clicks did nothing).

It took me a few days/hours of debugging to realize their fragments inherited from a lame BaseFragment that did "setRetainInstance = true". It was buried in an abstract method. Anyway, a spaghetti disaster. I am merely trying to raise awareness of how something so "simple" can have unintended consequences.

Should they be doing retain? NO
But they do and until it can be changed, one has to be aware of cases not considered.

Regarding the setRetainInstance deprecation, also to consider is that this was a relatively recent deprecation:

Version 1.3.0
February 10, 2021

setRetainInstance() deprecation: The setRetainInstance() method on Fragments has been deprecated. With the introduction of ViewModels, developers have a specific API for retaining state that can be associated with Activities, Fragments, and Navigation graphs. This allows developers to use a normal, not retained Fragment and keep the specific state they want retained separate, avoiding a common source of leaks while maintaining the useful properties of a single creation and destruction of the retained state (namely, the constructor of the ViewModel and the onCleared() callback it receives).

source: https://developer.android.com/jetpack/androidx/releases/fragment

At that case, that just means the AutoClearedValue I based this code on is conceptually flawed.

It could be or, in Google's words: "Working as intended" :p

@benjdero

This comment has been minimized.

Copy link

@benjdero benjdero commented Apr 23, 2021

Why have you removed the AppCompatActivity.viewBinding extension function?

I've read both your blogs posts "Simple one-liner ViewBinding in Fragments and Activities with Kotlin" and "An update to the FragmentViewBindingDelegate: the bug we’ve inherited from AutoClearedValue" but haven't found any information about this removal

@Drjacky

This comment has been minimized.

Copy link

@Drjacky Drjacky commented Apr 23, 2021

Why have you removed the AppCompatActivity.viewBinding extension function?

I've read both your blogs posts "Simple one-liner ViewBinding in Fragments and Activities with Kotlin" and "An update to the FragmentViewBindingDelegate: the bug we’ve inherited from AutoClearedValue" but haven't found any information about this removal

It wasn't there in first place: Zhuinden/fragmentviewbindingdelegate-kt@a7c1c94#diff-8e05f917a7f5d8b22b1f311a7bf9c2a9e1a35a08244356353f9b56a525cdf9b0

Use this:

inline fun <T : ViewBinding> AppCompatActivity.viewBinding(
    crossinline bindingInflater: (LayoutInflater) -> T
) =
    lazy(LazyThreadSafetyMode.NONE) {
        bindingInflater.invoke(layoutInflater)
    }
@VahidGarousi

This comment has been minimized.

Copy link

@VahidGarousi VahidGarousi commented Jun 15, 2021

Hello, I hope you are well.
I found a problem that I will explain step by step.

  1. Create a fragment called CruiseFilterFragment and then add a button to the page to click on it to go to the CruiseResultFragment fragment.
  2. Create a fragment called CruiseResultFragment. No special work is required in this fragment

3 - Click the button on the CruiseFilterFragment page to go to the next page.
4. On the CruiseResultFragment page, return to the CruiseFilterFragment page.

5 - Click the button again
You will encounter the following error:

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.pixelco.hitrav.dev, PID: 13616
    java.lang.IllegalStateException: Can't access the Fragment View's LifecycleOwner when getView() is null i.e., before onCreateView() or after onDestroyView()
        at androidx.fragment.app.Fragment.getViewLifecycleOwner(Fragment.java:361)
        at com.pixelco.hitrav.base.FragmentViewBindingDelegate.getValue(FragmentViewBindingDelegate.kt:49)
        at com.pixelco.hitrav.feature.product.cruise.result.CruiseResultFragment.getBinding(CruiseResultFragment.kt)
        at com.pixelco.hitrav.feature.product.cruise.result.CruiseResultFragment.access$getBinding$p(CruiseResultFragment.kt:20)
        at com.pixelco.hitrav.feature.product.cruise.result.CruiseResultFragment$setUpViews$2.invoke(CruiseResultFragment.kt:39)
        at com.pixelco.hitrav.feature.product.cruise.result.CruiseResultFragment$setUpViews$2.invoke(CruiseResultFragment.kt:20)
        at androidx.paging.PagingDataDiffer$processPageEventCallback$1.onStateUpdate(PagingDataDiffer.kt:92)
        at androidx.paging.PagePresenter.processEvent(PagePresenter.kt:100)
        at androidx.paging.PagingDataDiffer$collectFrom$2$invokeSuspend$$inlined$collect$1$lambda$1.invokeSuspend(PagingDataDiffer.kt:184)
        at androidx.paging.PagingDataDiffer$collectFrom$2$invokeSuspend$$inlined$collect$1$lambda$1.invoke(PagingDataDiffer.kt)
        at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:91)
        at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:165)
        at kotlinx.coroutines.BuildersKt.withContext(Unknown Source)
        at androidx.paging.PagingDataDiffer$collectFrom$2$invokeSuspend$$inlined$collect$1.emit(Collect.kt:133)
        at kotlinx.coroutines.flow.FlowKt__ChannelsKt.emitAllImpl$FlowKt__ChannelsKt(Channels.kt:61)
        at kotlinx.coroutines.flow.FlowKt__ChannelsKt$emitAllImpl$1.invokeSuspend(Channels.kt)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
        at kotlinx.coroutines.EventLoop.processUnconfinedEvent(EventLoop.common.kt:69)
        at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:357)
        at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:30)
        at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable$default(Cancellable.kt:27)
        at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:110)
        at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:158)
        at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(Builders.common.kt:56)
        at kotlinx.coroutines.BuildersKt.launch(Unknown Source)
        at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch$default(Builders.common.kt:49)
        at kotlinx.coroutines.BuildersKt.launch$default(Unknown Source)
        at com.pixelco.hitrav.feature.product.cruise.result.CruiseResultFragment.observeData(CruiseResultFragment.kt:51)
        at com.pixelco.hitrav.base.BaseFragment.onViewCreated(BaseFragment.kt:74)
        at androidx.fragment.app.Fragment.performViewCreated(Fragment.java:2987)
        at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:546)
        at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:282)
        at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:2189)
        at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:2106)
        at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:2002)
        at androidx.fragment.app.FragmentManager$5.run(FragmentManager.java:524)
        at android.os.Handler.handleCallback(Handler.java:739)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:148)
        at android.app.ActivityThread.main(ActivityThread.java:5417)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
@Zhuinden

This comment has been minimized.

Copy link
Owner Author

@Zhuinden Zhuinden commented Jun 15, 2021

@VahidGarousi it sounds like you aren't using viewLifecycleOwner.lifecycleScope.launchWhenStarted {.

@Uiasdnmb

This comment has been minimized.

Copy link

@Uiasdnmb Uiasdnmb commented Jun 19, 2021

I was actually writing this delegate myself and hit onDestroyView issue.

To access binding there I altered my delegate to use viewLifecycleOwnerLiveData.observeForever and releasing the binding when it becomes null instead of observing lifecycle events.

Observing forever looks bad but there's no lifecycleOwner to latch onto as both fragments lifecycle and views lifecycle get destroyed before onDestroyView so they'd invalidate the observer itself. However viewLifecycleOwnerLiveData is guaranteed to become null right after onDestroyView call so I won't worry about it.

I'm using fragment 1.3.5 (so new internal implementation).

@marlonlom

This comment has been minimized.

Copy link

@marlonlom marlonlom commented Jun 22, 2021

@Uiasdnmb

I was actually writing this delegate myself and hit onDestroyView issue.

... i think this onDestroyView kind-.of thing could cause a memory leak, i read about that while adding the leakcanary library in an app im working :S

@Uiasdnmb

This comment has been minimized.

Copy link

@Uiasdnmb Uiasdnmb commented Jun 22, 2021

You can see in fragment manager source how fragment view destruction is performed:

  1. performDestroyView - which is the place where lifecycle becomes destroyed and onDestroyView is called
  2. fragments view reference is set to null
  3. viewLifecycleOwnerLiveData value is set to null

I'd conclude that its impossible not to trigger the observer (as long as view itself was set) and say that Leakcanary is just wrong in this case as it's probably just hooking into lifecycle while being unaware of additional observer that'll clear the reference right after lifecycle is destroyed.

@MasterCluster

This comment has been minimized.

Copy link

@MasterCluster MasterCluster commented Jul 8, 2021

How about such a getValue version?

    override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
        return binding ?: run {
            val lifecycle = fragment.viewLifecycleOwner.lifecycle
            if (!lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) {
                throw IllegalStateException("Should not attempt to get bindings when Fragment views are destroyed.")
            }

            viewBindingFactory(thisRef.requireView()).also {
                this.binding = it
            }
        }
    }

to get rid of

    val binding = binding
    if (binding != null) {
        return binding
    }

@Uiasdnmb

This comment has been minimized.

Copy link

@Uiasdnmb Uiasdnmb commented Jul 10, 2021

I just had an idea while checking how findViewTreeLifecycleOwner method works - it just tags root view with LifecycleOwner.
I think that's an interesting alternative for this use as well:

class VBDelegate<T>(private val bindingFactory: (View) -> T) : ReadOnlyProperty<Fragment, T> {
    companion object {
        private val key = R.id.content
    }

    override fun getValue(thisRef: Fragment, property: KProperty<*>): T =
        thisRef.requireView().run {
            getTag(key)?.let { it as T } ?: bindingFactory(this).also { setTag(key, it) }
        }
}

Now there are no listeners, no potential leaks (binding reference is cleared alongside view itself) and you can access it during onDestroyView safely.
Only caveat is (if you can call it that) you need to use project specific resource ID for tagging purpose.

@Drjacky

This comment has been minimized.

Copy link

@Drjacky Drjacky commented Oct 21, 2021

@ZacSweers

Fatal Exception: java.lang.IllegalStateException: Can't access the Fragment View's LifecycleOwner when getView() is null i.e., before onCreateView() or after onDestroyView()
       at androidx.fragment.app.Fragment.getViewLifecycleOwner(Fragment.java:361)
       at net.example.company.utils.views.FragmentViewBindingDelegate.getValue(FragmentViewBindingDelegate.java:61)
       at net.example.company.views.smartdogtrainer.common.FragBlah.getBinding(FragBlah.java:80)
       at net.example.company.views.smartdogtrainer.common.FragBlah.visibilityConnectingContainer(FragBlah.java:295)
       at net.example.company.views.smartdogtrainer.common.FragBlah.resetVisibility(FragBlah.java:289)
       at net.example.company.views.smartdogtrainer.common.FragBlah.showErrorConnection(FragBlah.java:311)
       at net.example.company.views.smartdogtrainer.common.FragBlah.performErrorConnection$lambda-8$lambda-7(FragBlah.java:332)
       at net.example.company.views.smartdogtrainer.common.FragBlah$$InternalSyntheticLambda$0$deef3459a1d7d120afdfd9de73e823d74825ffa0a9101b2fc6484805f55a8632$0.run$bridge(FragBlah.java:13)
       at android.app.Activity.runOnUiThread(Activity.java:7154)
       at net.example.company.views.smartdogtrainer.common.FragBlah.performErrorConnection(FragBlah.java:331)
       at net.example.company.views.smartdogtrainer.common.FragBlah.access$performErrorConnection(FragBlah.java:63)
       at net.example.company.views.smartdogtrainer.common.FragBlah$sdtCollarScannerCallback$1.onStopScanning(FragBlah.java:120)
       at net.example.company.scanner.BleScanner.stopScanningForDevices(BleScanner.java:128)
       at net.example.company.scanner.BleScanner.durationTimeoutScanning(BleScanner.java:116)
       at net.example.company.scanner.BleScanner.access$durationTimeoutScanning(BleScanner.java:15)
       at net.example.company.scanner.BleScanner$durationScanningRunnable$1.run(BleScanner.java:52)
       at android.os.Handler.handleCallback(Handler.java:938)
       at android.os.Handler.dispatchMessage(Handler.java:99)
       at android.os.Looper.loop(Looper.java:246)
       at android.app.ActivityThread.main(ActivityThread.java:8595)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130)

And my delegate:

class FragmentViewBindingDelegate<T : ViewBinding>(
    val fragment: Fragment,
    val viewBindingFactory: (Fragment) -> T,
    val cleanUp: ((T?) -> Unit)?
) : ReadOnlyProperty<Fragment, T> {

    // A backing property to hold our value
    private var binding: T? = null

    init {
        fragment.lifecycle.addObserver(object : DefaultLifecycleObserver {
            val viewLifecycleOwnerLiveDataObserver =
                Observer<LifecycleOwner?> {
                    val viewLifecycleOwner = it ?: return@Observer

                    viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
                        override fun onDestroy(owner: LifecycleOwner) {
                            cleanUp?.invoke(binding)
                            binding = null
                        }
                    })
                }

            override fun onCreate(owner: LifecycleOwner) {
                fragment.viewLifecycleOwnerLiveData.observeForever(
                    viewLifecycleOwnerLiveDataObserver
                )
            }

            override fun onDestroy(owner: LifecycleOwner) {
                fragment.viewLifecycleOwnerLiveData.removeObserver(
                    viewLifecycleOwnerLiveDataObserver
                )
            }
        })
    }

    override fun getValue(
        thisRef: Fragment,
        property: KProperty<*>
    ): T {
        val binding = binding
        if (binding != null) {
            return binding
        }

        val lifecycle = fragment.viewLifecycleOwner.lifecycle
        if (lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED).not()) {
            throw IllegalStateException("Should not attempt to get bindings when Fragment views are destroyed.")
        }

        return viewBindingFactory(thisRef).also { this.binding = it }
    }
}

inline fun <T : ViewBinding> Fragment.viewBinding(
    crossinline viewBindingFactory: (View) -> T,
    noinline cleanUp: ((T?) -> Unit)? = null
): FragmentViewBindingDelegate<T> =
    FragmentViewBindingDelegate(this, { f -> viewBindingFactory(f.requireView()) }, cleanUp)

inline fun <T : ViewBinding> Fragment.viewInflateBinding(
    crossinline bindingInflater: (LayoutInflater) -> T,
    noinline cleanUp: ((T?) -> Unit)? = null,
): FragmentViewBindingDelegate<T> =
    FragmentViewBindingDelegate(this, { f -> bindingInflater(f.layoutInflater) }, cleanUp)

inline fun <T : ViewBinding> AppCompatActivity.viewInflateBinding(
    crossinline bindingInflater: (LayoutInflater) -> T
) =
    lazy(LazyThreadSafetyMode.NONE) {
        bindingInflater.invoke(layoutInflater)
    }
@Zhuinden

This comment has been minimized.

Copy link
Owner Author

@Zhuinden Zhuinden commented Oct 21, 2021

@Drjacky sounds like you are missing val binding = binding in your onViewCreated OR you are actually accessing the view after onDestroyView and potentially have a memory leak in your app anyway.

@Drjacky

This comment has been minimized.

Copy link

@Drjacky Drjacky commented Oct 21, 2021

@Zhuinden You mean just this:

override fun onViewCreated(view: View, savedInstanceState: Bundle) {
    super.onViewCreated(view, savedInstanceState)
    val binding = binding
}

But still using the binding from the top variable in lines that are outside of the onViewCreated:

class FragBlah : Fragment() {
    private val binding by viewInflateBinding(FragBlahBinding::inflate)
...
private fun visibilityConnectingContainer() {
    binding.txtTitle.visibilityOff() //this binding is the one from the top class variable
}

?

@Zhuinden

This comment has been minimized.

Copy link
Owner Author

@Zhuinden Zhuinden commented Oct 21, 2021

In that case, the problem is that you have some listener that is called even after onDestroyView.

That's not a bug in this code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment