Mobile Development 12 min read

Comprehensive Guide to Listening to Android Fragment Visibility

This article presents a complete solution for monitoring Fragment visibility in Android, covering simple replace operations, hide/show handling, ViewPager nesting, AndroidX adapter behaviors, and a reusable BaseVisibilityFragment implementation with Kotlin code examples.

Sohu Tech Products
Sohu Tech Products
Sohu Tech Products
Comprehensive Guide to Listening to Android Fragment Visibility

This article provides a practical solution for listening to Fragment visibility in Android, covering a variety of common scenarios and offering reusable Kotlin code.

1. Replace operation – When a Fragment is the only one on a page and is replaced, the normal lifecycle callbacks onResume and onPause are invoked. By adding a simple check in these methods the visibility can be detected:

override fun onResume() {
    info("onResume")
    super.onResume()
    onActivityVisibilityChanged(true)
}

override fun onPause() {
    info("onPause")
    super.onPause()
    onActivityVisibilityChanged(false)
}

2. Hide / Show operations – Adding or replacing a Fragment triggers lifecycle callbacks, but hide/show does not. The method onHiddenChanged can be used to monitor visibility changes:

override fun onHiddenChanged(hidden: Boolean) {
    super.onHiddenChanged(hidden)
    checkVisibility(hidden)
}

3. ViewPager nested Fragment – Because ViewPager pre‑loads pages, onResume is not reliable. The deprecated setUserVisibleHint (still usable) indicates when a Fragment becomes visible (true) or is switched away (false):

override fun setUserVisibleHint(isVisibleToUser: Boolean) {
    info("setUserVisibleHint = $isVisibleToUser")
    super.setUserVisibleHint(isVisibleToUser)
    checkVisibility(isVisibleToUser)
}

Note that this callback may be invoked before the Fragment’s lifecycle methods, so a guard check is required.

4. AndroidX adapter adaptation – In AndroidX the constructors of FragmentPagerAdapter and FragmentStatePagerAdapter accept a behavior flag. Two values are relevant:

BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT : only the currently displayed Fragment reaches onResume ; others stop at onStart . The method setUserVisibleHint is no longer called.

BEHAVIOR_SET_USER_VISIBLE_HINT : the old behavior is kept, and visibility is still reported via setUserVisibleHint .

Relevant source snippets:

public FragmentStatePagerAdapter(@NonNull FragmentManager fm, @Behavior int behavior) {
    mFragmentManager = fm;
    mBehavior = behavior;
}

@Override
public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
    Fragment fragment = (Fragment) object;
    if (fragment != mCurrentPrimaryItem) {
        if (mCurrentPrimaryItem != null) {
            mCurrentPrimaryItem.setMenuVisibility(false);
            if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
                if (mCurTransaction == null) {
                    mCurTransaction = mFragmentManager.beginTransaction();
                }
                mCurTransaction.setMaxLifecycle(mCurrentPrimaryItem, Lifecycle.State.STARTED);
            } else {
                mCurrentPrimaryItem.setUserVisibleHint(false);
            }
        }
        fragment.setMenuVisibility(true);
        if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
            if (mCurTransaction == null) {
                mCurTransaction = mFragmentManager.beginTransaction();
            }
            mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED);
        } else {
            fragment.setUserVisibleHint(true);
        }
        mCurrentPrimaryItem = fragment;
    }
}

5. Full reusable implementation – The article finally presents a complete solution consisting of an interface and a base class that can be extended by any Fragment that needs visibility tracking.

interface OnFragmentVisibilityChangedListener {
    fun onFragmentVisibilityChanged(visible: Boolean)
}

open class BaseVisibilityFragment : Fragment(), View.OnAttachStateChangeListener,
    OnFragmentVisibilityChangedListener {

    private var parentActivityVisible = false
    private var visible = false
    private var localParentFragment: BaseVisibilityFragment? = null
    private val listeners = ArrayList
()

    fun addOnVisibilityChangedListener(listener: OnFragmentVisibilityChangedListener?) {
        listener?.let { listeners.add(it) }
    }

    fun removeOnVisibilityChangedListener(listener: OnFragmentVisibilityChangedListener?) {
        listener?.let { listeners.remove(it) }
    }

    override fun onAttach(context: Context) {
        super.onAttach(context)
        val parent = parentFragment
        if (parent != null && parent is BaseVisibilityFragment) {
            localParentFragment = parent
            parent.addOnVisibilityChangedListener(this)
        }
        checkVisibility(true)
    }

    override fun onDetach() {
        localParentFragment?.removeOnVisibilityChangedListener(this)
        super.onDetach()
        checkVisibility(false)
        localParentFragment = null
    }

    override fun onResume() {
        super.onResume()
        onActivityVisibilityChanged(true)
    }

    override fun onPause() {
        super.onPause()
        onActivityVisibilityChanged(false)
    }

    protected fun onActivityVisibilityChanged(visible: Boolean) {
        parentActivityVisible = visible
        checkVisibility(visible)
    }

    override fun onFragmentVisibilityChanged(visible: Boolean) {
        checkVisibility(visible)
    }

    override fun onHiddenChanged(hidden: Boolean) {
        super.onHiddenChanged(hidden)
        checkVisibility(!hidden)
    }

    override fun setUserVisibleHint(isVisibleToUser: Boolean) {
        super.setUserVisibleHint(isVisibleToUser)
        checkVisibility(isVisibleToUser)
    }

    private fun checkVisibility(expected: Boolean) {
        if (expected == visible) return
        val parentVisible = localParentFragment?.isFragmentVisible() ?: parentActivityVisible
        val superVisible = super.isVisible()
        val hintVisible = userVisibleHint
        val newVisible = parentVisible && superVisible && hintVisible
        if (newVisible != visible) {
            visible = newVisible
            onVisibilityChanged(visible)
        }
    }

    protected fun onVisibilityChanged(visible: Boolean) {
        listeners.forEach { it.onFragmentVisibilityChanged(visible) }
    }

    fun isFragmentVisible(): Boolean = visible

    private fun info(s: String) {
        Log.i(TAG, "${javaClass.simpleName} ; $s ; this is $this")
    }
}

This implementation supports four main cases: ViewPager nesting with setUserVisibleHint , direct add/hide with onHiddenChanged , replace with onResume checks, and nested ViewPager scenarios where both parent and child Fragments inherit from BaseVisibilityFragment . The article also links to further reading on Fragment visibility handling.

AndroidKotlinlifecycleFragmentVisibilityViewPager
Sohu Tech Products
Written by

Sohu Tech Products

A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.