1

I have a ViewPager with 4 fragments (a,b,c,d), both c and d both have ViewPagers, essentially nested view pagers. I've attached an image to illustrate my layout. So when I select Fragment B, setUserVisibleHint(boolean)is true and setUserVisibleHint(boolean) in Fragment C is false which is the expected behaviour. However, even though setUserVisibleHint(boolean) in Fragment C is false, setUserVisibleHint(boolean) in the first fragment in Fragment C ViewPager is true.

I don't think that this is expected behaviour. Can someone please advise what may be going on here? I'd greatly appreciate any input.

enter image description here

azizbekian
  • 60,783
  • 13
  • 169
  • 249
DJ-DOO
  • 4,545
  • 15
  • 58
  • 98

2 Answers2

0

From docs of setUserVisibleHint(boolean):

Set a hint to the system about whether this fragment's UI is currently visible to the user. This hint defaults to true and is persistent across fragment instance state save and restore.

An app may set this to false to indicate that the fragment's UI is scrolled out of visibility or is otherwise not directly visible to the user. This may be used by the system to prioritize operations such as fragment lifecycle updates or loader ordering behavior.

As can be outlined from the docs:

  • This value defaults to true
  • This value is not being set automatically by framework. It's "the app" that may or may not set this flag.

If you look into the usages of setUserVisibleHint(boolean), you'll see following list:

enter image description here

Thus, only PagerAdapter implementations are calling that method. As you have outlined, PagerAdapter is correctly setting that flag for all the fragment that it possesses.

PagerAdapter is non-wiser about child fragments, that fragment C has. It's not the responsibility of PagerAdapter to set that flag for child fragment of fragment C. It's the responsibility of "the app" (in this case you) to manually set that boolean value.

Community
  • 1
  • 1
azizbekian
  • 60,783
  • 13
  • 169
  • 249
  • I thought as fragment c has a `PagerAdapter` that it should handle the flags of the fragments within that `PagerAdapter`? – DJ-DOO Feb 01 '18 at 20:46
  • When I navigate to Fragment b & manually set the boolean `userVisibleHint = false` it sets correctly, however `setUserVisibleHint(boolean)` is hit 3 times, the first to times `isVisibleToUser` is `false`, the third time it reverts to `true` it should never be true until it is actually visible, so I'm not sure a. why it's been called `setUserVisibleHint(boolean)` is called 3 times and b why it's set to true – DJ-DOO Feb 01 '18 at 21:07
  • Can anyone please tell me a definitive way to identify when a fragment of a view pager within a fragment of a view pager becomes visible. I have tried `onPageSelected()` I have tried `setUserVisibleHint(boolean)` and I've tried `setPrimaryItem(container: ViewGroup, position: Int, `object`: Any)` This is becoming very frustrating as I need to identify when the fragment is visible to the user in order to track usage. – DJ-DOO Feb 02 '18 at 08:02
  • 1
    See [this](https://stackoverflow.com/questions/43207043/check-if-fragment-is-currently-visible-or-no/43652401#43652401) answer. – azizbekian Feb 02 '18 at 08:34
0

You should check if the parent is fragment and visible or isn't fragment then start your logic.

public class Child1FragmentC extend Fragment {
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (isVisibleToUser && isParentFragmentVisible())
            //TODO: start your logic 
    }

    /**
     *
     * @return true if the parent is fragment and visible or the parent isn't fragment
     */
    boolean isParentFragmentVisible() {
        return getParentFragment() == null || getParentFragment().getUserVisibleHint();
    }
}

Now when the platform will call the first fragment on fragment C, it will ask it's parent fragment C and it will be invisible so it won't start it's logic.

Also when fragment C visibility changed the platform won't notify the first fragment on fragment c that it's visibility changed because the platform told it before and we didn't start the logic because the parent was invisible.

So we need to make the fragment c when it's visibility changed to notify the current visible fragment in it's view pager(first fragment of fragment C) that the visibility changed.

public class FragmentC extend Fragment {
    /**
     * Notify the current selected fragment inside view pager that it's visibility changed.
     *
     * @param isVisibleToUser
     */
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (isVisibleToUser)
            //TODO: start your logic 

            //Notify the current fragment in view pager the the isVisibleToUser changed.
            Fragment currentFragment = getChildFragmentManager().findFragmentByTag(
                    "android:switcher:" + getViewPagerId() + ":" + getViewPager().getCurrentItem());
            if (currentFragment != null)
                currentFragment.setUserVisibleHint(isVisibleToUser);

    }
}

Another thing you should handle is the platform call the setUserVisibilityHint before view is created so you should check that the view is prepared by checking that getView() != null or any other conditions.

So the final code will be like this.

public abstract class BaseFragment extend Fragment {

    /**
     * start logic if isVisibleToUser, isViewPrepared and isParentFragmentVisible equal true.
     *
     * => We check isViewPrepared equal true before starting logic because setUserVisibleHint
     * called before all fragment lifecycle callbacks. So we should check if the view is
     * prepared.
     * 
     * => We check if the parent is visible to solve if we have a fragment inside a view pager and 
     * that fragment also has view pager that contains children fragments but this fragment isn't
     * visible right now. the platform will call the first child fragment in the view pager that
     * it's visibility is true.
     *
     * @param isVisibleToUser
     */
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (isVisibleToUser && isViewPrepared() && isParentFragmentVisible())
            //TODO: start your logic 
    }

    /**
     *
     * @return true if the parent is fragment and visible or the parent isn't fragment
     */
    boolean isParentFragmentVisible() {
        return getParentFragment() == null || getParentFragment().getUserVisibleHint();
    }

    /**
     *
     * @return true if the view isn't null
     */
    boolean isViewPrepared() {
        return getView() != null;
    }
}


/**
 * Use this base fragment when you have fragment that has view pager and you want to handle
 * when child fragments become actually visible to the user to start the logic.
 */
public abstract class BaseBootstrapHostViewPagerFragment extends BaseFragment {

    /**
     * Notify the current selected fragment inside view pager that it's setUserVisibleHint changed.
     */
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);

        if (isViewPrepared()) {
            Fragment currentFragment = getChildFragmentManager().findFragmentByTag(
                    "android:switcher:" + getViewPagerId() + ":" + getViewPager().getCurrentItem());
            if (currentFragment != null)
                currentFragment.setUserVisibleHint(isVisibleToUser);
        }

    }

    public abstract int getViewPagerId();

    public abstract ViewPager getViewPager();
}

public FragmentC extend BaseBootstrapHostViewPagerFragment {....}

public Child1FragmentC extend BaseFragment {....}
Ashraf Atef
  • 401
  • 4
  • 9