95

I'm using the support library v4 and my questions are, How to know if a Fragment is Visible? and How can I change the propierties of the Layout inflated in the Fragment?

I'm using fragments like in the android developers tutorial with a FragmentActivity.

starball
  • 20,030
  • 7
  • 43
  • 238

15 Answers15

126

You should be able to do the following:

MyFragmentClass test = (MyFragmentClass) getSupportFragmentManager().findFragmentByTag("testID");
if (test != null && test.isVisible()) {
     //DO STUFF
}
else {
    //Whatever
}
TronicZomB
  • 8,667
  • 7
  • 35
  • 50
60

Both isVisible() and isAdded() return true as soon as the Fragment is created, and not even actually visible. The only solution that actually works is:

if (isAdded() && isVisible() && getUserVisibleHint()) {
    // ... do your thing
}

This does the job. Period.

NOTICE: getUserVisibleHint() is now deprecated. be careful.

Tobliug
  • 2,992
  • 30
  • 28
Yash Sampat
  • 30,051
  • 12
  • 94
  • 120
  • 1
    I spend a lot of time just for this getUserVisibleHint() THX – Ares91 Aug 04 '17 at 15:02
  • 1
    yeah, lets bring it to top – LamaUltramarine Jan 16 '19 at 13:48
  • 1
    I think this answer is the most correct solution. This would be accepted answer – DrMorteza Mar 11 '19 at 20:37
  • 2
    isVisible check isAdded thus isAdded is redundant here – Shubham AgaRwal Mar 25 '19 at 06:39
  • @Killer: true, but it doesn't always work in all situations unless we explicitly add isVisible() – Yash Sampat Mar 31 '19 at 16:20
  • 2
    isVisible comes after isAdded. So could you give a scenario where isAdded will not work as you are mentioning both isAdded and isVisible @Y.S. – Sp4Rx Apr 05 '19 at 20:40
  • @Sp4Rx: As I have mentioned in my answer, both isVisible() and isAdded() return true as soon as the Fragment is created, and not even actually visible. – Yash Sampat Apr 06 '19 at 09:15
  • My bad if I had miss interpreted. isVisible() says that _Return true if the fragment is currently visible to the user. This means it: (1) has been added, (2) has its view attached to the window, and (3) is not hidden_ and I also agree what @Killer has said in his comment that isAdded() is redundant when isVisible() is checked. So can we have `isVisible() && getUserVisibleHint()` ? @Y.S. – Sp4Rx Apr 06 '19 at 09:34
  • 1
    @Sp4Rx: You can try that, but I would advise you to test it thoroughly in all situations. My experience was that this is what works correctly all the time. – Yash Sampat Apr 06 '19 at 13:36
  • 20
    getUserVisibleHint() is now deprecated - what is the alternative? – Morten Holmgaard Jun 20 '19 at 06:23
46

If you want to know when use is looking at the fragment you should use

yourFragment.isResumed()

instead of

yourFragment.isVisible()

First of all isVisible() already checks for isAdded() so no need for calling both. Second, non-of these two means that user is actually seeing your fragment. Only isResumed() makes sure that your fragment is in front of the user and user can interact with it if thats whats you are looking for.

Kayvan N
  • 8,108
  • 6
  • 29
  • 38
  • 5
    Beautiful answer and explanation, +1 a million times if i could – Aniruddha K.M Jun 23 '17 at 06:30
  • 14
    This does'nt work. It gives me true for all tabs evn though only one of them is in use and visible. – leoOrion Jun 27 '17 at 05:22
  • 1
    It seems not working. isResumed still returns 'true', even the fragment is in back stack and not visible at all. I checked 'isResumed' on 'onPrepareOptionsMenu' method and I'm using getChildFragmentManager for child fragments in the view pager. – wonsuc Sep 12 '17 at 18:29
  • @leoOrion I have the same situation – Konstantin Konopko Mar 08 '18 at 17:35
  • @KonstantinKonopko the getUserVisibleHint() is what will tell you if its visible to user. isVisible() will return true even if the fragment is in backOfStack but not visible to user. – leoOrion Mar 09 '18 at 01:15
  • @leoOrion in my case each fragment returns `getUserVisibleHint()` is true, weird %( – Konstantin Konopko Mar 10 '18 at 13:16
  • 1
    If you are using ViewPager, you should note that ViewPager is implemented to initialize not only the visible fragment but also the ones that are immediate left and right of the current visible fragment. Those fragments are not in "backstack". They are fully resumed and visible in program's perspective, it is just that they are not visible on the screen. – Sanlok Lee Mar 19 '19 at 22:10
19

you can try this way:

Fragment currentFragment = getFragmentManager().findFragmentById(R.id.fragment_container);

or

Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);

In this if, you check if currentFragment is instance of YourFragment

if (currentFragment instanceof YourFragment) {
     Log.v(TAG, "your Fragment is Visible");
}
Cabezas
  • 9,329
  • 7
  • 67
  • 69
16

You can override setMenuVisibility like this:

@Override
public void setMenuVisibility(final boolean visible) {
   if (visible) {
      //Do your stuff here
   }

   super.setMenuVisibility(visible);
}
Mikel
  • 1,581
  • 17
  • 35
11

getUserVisibleHint() comes as true only when the fragment is on the view and visible

IntelliJ Amiya
  • 74,896
  • 15
  • 165
  • 198
Mitech
  • 400
  • 4
  • 6
9

One thing to be aware of, is that isVisible() returns the visible state of the current fragment. There is a problem in the support library, where if you have nested fragments, and you hide the parent fragment (and therefore all the children), the child still says it is visible.

isVisible() is final, so can't override unfortunately. My workaround was to create a BaseFragment class that all my fragments extend, and then create a method like so:

public boolean getIsVisible()
{
    if (getParentFragment() != null && getParentFragment() instanceof BaseFragment)
    {
        return isVisible() && ((BaseFragment) getParentFragment()).getIsVisible();
    }
    else
    {
        return isVisible();
    }
}

I do isVisible() && ((BaseFragment) getParentFragment()).getIsVisible(); because we want to return false if any of the parent fragments are hidden.

This seems to do the trick for me.

logan
  • 430
  • 5
  • 11
  • Thank you! Wasted 2 hours scratching my head over this. – fhucho Jan 16 '15 at 16:10
  • What if the parent fragment is visible but the nested fragment for which one is checking is not visible?.. How do we check for such a scenario? – leoOrion Jun 27 '17 at 05:24
4
ArticleFragment articleFrag = (ArticleFragment)
            getSupportFragmentManager().findFragmentById(R.id.article_fragment);

    if (articleFrag != null && articleFrag.isVisible()) {

        // Call a method in the ArticleFragment to update its content
        articleFrag.updateArticleView(position);
    }

see http://developer.android.com/training/basics/fragments/communicating.html

Alex
  • 591
  • 5
  • 8
2

Adding some information here that I experienced:

fragment.isVisible is only working (true/false) when you replaceFragment() otherwise if you work with addFragment(), isVisible always returns true whether the fragment is in behind of some other fragment.

Amir Raza
  • 2,320
  • 1
  • 23
  • 32
2

Just in case you use a Fragment layout with a ViewPager (TabLayout), you can easily ask for the current (in front) fragment by ViewPager.getCurrentItem() method. It will give you the page index.

Mapping from page index to fragment[class] should be easy as you did the mapping in your FragmentPagerAdapter derived Adapter already.

int i = pager.getCurrentItem();

You may register for page change notifications by

ViewPager pager = (ViewPager) findViewById(R.id.container);
pager.addOnPageChangeListener(this);

Of course you must implement interface ViewPager.OnPageChangeListener

public class MainActivity 
    extends AppCompatActivity
    implements ViewPager.OnPageChangeListener
{
    public void onPageSelected (int position)
    {
        // we get notified here when user scrolls/switches Fragment in ViewPager -- so
        // we know which one is in front.
        Toast toast = Toast.makeText(this, "current page " + String.valueOf(position), Toast.LENGTH_LONG);
        toast.show();
    }

    public void onPageScrolled (int position, float positionOffset, int positionOffsetPixels) {
    }

    public void onPageScrollStateChanged (int state) {
    }
}

My answer here might be a little off the question. But as a newbie to Android Apps I was just facing exactly this problem and did not find an answer anywhere. So worked out above solution and posting it here -- perhaps someone finds it useful.

Edit: You might combine this method with LiveData on which the fragments subscribe. Further on, if you give your Fragments a page index as constructor argument, you can make a simple amIvisible() function in your fragment class.

In MainActivity:

private final MutableLiveData<Integer> current_page_ld = new MutableLiveData<>();
public LiveData<Integer> getCurrentPageIdx() { return current_page_ld; }

public void onPageSelected(int position) {
    current_page_ld.setValue(position);
}

public class MyPagerAdapter extends FragmentPagerAdapter
{
    public Fragment getItem(int position) {
        // getItem is called to instantiate the fragment for the given page: But only on first
        // creation -- not on restore state !!!
        // see: https://stackoverflow.com/a/35677363/3290848
        switch (position) {
            case 0:
                return MyFragment.newInstance(0);
            case 1:
                return OtherFragment.newInstance(1);
            case 2:
                return XYFragment.newInstance(2);
        }
        return null;
    }
}

In Fragment:

    public static MyFragment newInstance(int index) {
        MyFragment fragment = new MyFragment();
        Bundle args = new Bundle();
        args.putInt("idx", index);
        fragment.setArguments(args);
        return fragment;
    }

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            mPageIndex = getArguments().getInt(ARG_PARAM1);
        }
        ...
    }
    
    public void onAttach(Context context)
    {
        super.onAttach(context);
        MyActivity mActivity = (MyActivity)context;

        mActivity.getCurrentPageIdx().observe(this, new Observer<Integer>() {
            @Override
            public void onChanged(Integer data) {
                if (data == mPageIndex) {
                    // have focus
                } else {
                    // not in front
                }
            }
        });
    }
NorbertM
  • 1,226
  • 12
  • 22
1

None of the above solutions worked for me. The following however works like a charm:-

override fun setUserVisibleHint(isVisibleToUser: Boolean)
Ashish Sharma
  • 566
  • 5
  • 20
Vaibhav Gupta
  • 89
  • 1
  • 6
1

Try this if you have only one Fragment

if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
                    //TODO: Your Code Here
                }

Vishnu
  • 663
  • 3
  • 7
  • 24
1

getUserVisibleHint is now deprecated, and I was having problems with isVisible being true when another fragment was added in front of it. This detects the fragment's visibility on the back stack using its view. This may be helpful if your issue is related to other fragments on the back stack.

View extension to detect if a view is being displayed on the screen: (see also How can you tell if a View is visible on screen in Android?)

fun View.isVisibleOnScreen(): Boolean {
    if (!isShown) return false
    val actualPosition = Rect().also { getGlobalVisibleRect(it) }
    val screenWidth = Resources.getSystem().displayMetrics.widthPixels
    val screenHeight = Resources.getSystem().displayMetrics.heightPixels
    val screen = Rect(0, 0, screenWidth, screenHeight)
    return Rect.intersects(actualPosition, screen)
}

Then defined a back stack listener from the fragment, watching the top fragment on the stack (the one added last)

fun Fragment.setOnFragmentStackVisibilityListener(onVisible: () -> Unit) {
    val renderDelayMillis = 300L
    parentFragmentManager.addOnBackStackChangedListener {
        Handler(Looper.getMainLooper()).postDelayed({
            if (isAdded) {
                val topStackFragment = parentFragmentManager.fragments[parentFragmentManager.fragments.size - 1]
                if (topStackFragment.view == view && isVisible && view!!.isVisibleOnScreen()) {
                    onVisible.invoke()
                }
            }
        }, renderDelayMillis)
    }
}

The back stack listener is called before the view is ready so an arbitrarily small delay was needed. The lambda is called when the view becomes visible.

Kabliz
  • 310
  • 4
  • 12
0

I was using Android's BottomNavigationView and managing fragments with FragmentTransactions.hide(frag) and FragmentTransaction.show(frag). So, to detect if a fragment is visible or not, I used following:

abstract class BaseFragment : Fragment() {

    open fun onFragmentVisible(){
        
    }
    
    override fun onStart() {
        super.onStart()
        if (!isHidden){
            onFragmentVisible()
        }
    }

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

You can extend BaseFragment in your fragment and implement it's onFragmentVisible function.

VishnuPrajapati
  • 119
  • 1
  • 6
0

In Kotlin

if you use FragmentPagerAdapter and since getUserVisibleHint() is deprecated in api 29, I suggest you to add behaviour parameter BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT in your FragmentPagerAdapter like this:

FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)

then in your fragment you can check using their lifecycle state:

if(lifecycle.currentState == Lifecycle.State.RESUMED) {
  // do something when fragment is visible
}
Fuad Reza
  • 161
  • 2
  • 6