Anton Cherkashyn
Anton Cherkashyn

Reputation: 5859

How to make custom view observe containing fragment's lifecycle events instead of activity?

I have an activity and several fragments that it replaces inside a frame layout. Each fragment contains a layout inflated from XML with a number of custom views. Inside those views, I want to subscribe to lifecycle events in these views using LifecycleObserver. My view in Kotlin:

class MyView(context: Context) : View(context, null, 0): LifecycleObserver {

    init {
        (getContext() as LifecycleOwner).lifecycle.addObserver(this)
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    fun onResume() {
        // code
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    fun onPause() {
        // code
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    fun onDestroy() {
        lifecycle.removeObserver(this)
    }

}

The problem is, when one fragment goes away and is replaced by another, the views in the first fragment don't receive an onPause event. Nor do they get onResume when I return to it from the second fragment. The views receive onPause only when the whole activity is paused, but they are unaware of lifecycle changes in the fragments. I tracked this down to layout inflater, that is used to inflate fragment's xml layout, it passes the activity as context parameter to views. This is how layout inflater is instantiated in support library's Fragment class:

/** @deprecated */
@Deprecated
@NonNull
@RestrictTo({Scope.LIBRARY_GROUP})
public LayoutInflater getLayoutInflater(@Nullable Bundle savedFragmentState) {
    if (this.mHost == null) {
        throw new IllegalStateException("onGetLayoutInflater() cannot be executed until the Fragment is attached to the FragmentManager.");
    } else {
        LayoutInflater result = this.mHost.onGetLayoutInflater();
        this.getChildFragmentManager();
        LayoutInflaterCompat.setFactory2(result, this.mChildFragmentManager.getLayoutInflaterFactory());
        return result;
    }
}

The mHost is a FragmentActivity that contains this fragment. Hence, LayoutInflater that's passed into fragment's onCreateView() contains a reference to the FragmentActivity as a context. So the views effectively observe Activity lifecycle.

How do I make my custom views observe lifecycle events of their containing fragment?

Upvotes: 1

Views: 5547

Answers (1)

TheWanderer
TheWanderer

Reputation: 17854

If you're only using onPause() and onResume(), overriding onDetachedFromWindow() and onAttachedToWindow() in your View class should be enough:

override fun onAttachedToWindow() {
    //onResume code
}

override fun onDetachedFromWindow() {
    //onPause code
}

You could also make your own lifecycle methods:

fun onResume() {}
fun onPause() {}

From your Fragment, you can retain a global reference to your View:

//`by lazy` only initializes the variable when it's fist called, 
//and then that instance is used for subsequent calls. 
//Make sure you only reference `myView` after `onCreateView()` returns
private val myView by lazy { view.findViewById<MyView>(R.id.my_view) }

And then from your Fragment's onPause() and onResume(), call your View's corresponding methods:

override fun onPause() {
    myView.onPause()
}

override fun onResume() {
    myView.onResume()
}

EDIT:

For expandability, make your own min-SDK. Create a base Fragment class:

open class LifecycleFragment : Fragment() {
    internal fun dispatchOnResume(parent: View) {
        if (parent is CustomLifecycleObserver) parent.onResume()
        if (parent is ViewGroup) {
            for (i in 0 until parent.childCount) {
                dispatchOnResume(parent.getChildAt(i))
            }
        }
    }

    internal fun dispatchOnPause(parent: View) {
        if (parent is CustomLifecycleObserver) parent.onPause()
        if (parent is ViewGroup) {
            for (i in 0 until parent.childCount) {
                dispatchOnResume(parent.getChildAt(i))
            }
        }
    }

    override fun onResume() {
        dispatchOnResume(view)
    }

    override fun onPause() {
        dispatchOnPause(view)
    }
}

(CustomLifecycleListener would be an interface for your Views to implement, containing onResume() and onPause() methods.)

Then just extend that class in your other Fragments:

class SomeFragment : LifecycleFragment() {}

Upvotes: 1

Related Questions