How to know if a Fragment is Visible?
AndroidAndroid FragmentsAndroid Problem Overview
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? Thanks in advance.
---Edit---
I'm using fragments like in the android developers tutorial with a FragmentActivity
Android Solutions
Solution 1 - Android
You should be able to do the following:
MyFragmentClass test = (MyFragmentClass) getSupportFragmentManager().findFragmentByTag("testID");
if (test != null && test.isVisible()) {
//DO STUFF
}
else {
//Whatever
}
Solution 2 - Android
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.
Solution 3 - Android
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.
Solution 4 - Android
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");
}
Solution 5 - Android
You can override setMenuVisibility like this:
@Override
public void setMenuVisibility(final boolean visible) {
if (visible) {
//Do your stuff here
}
super.setMenuVisibility(visible);
}
Solution 6 - Android
getUserVisibleHint()
comes as true only when the fragment is on the view and visible
Solution 7 - Android
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.
Solution 8 - Android
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
Solution 9 - Android
Try this if you have only one Fragment
if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
//TODO: Your Code Here
}
Solution 10 - Android
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
}
}
});
}
Solution 11 - Android
None of the above solutions worked for me. The following however works like a charm:-
override fun setUserVisibleHint(isVisibleToUser: Boolean)
Solution 12 - Android
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.
Solution 13 - Android
getUserVisibleHint
is now deprecated, and I was having problems with isVisible
being true when another fragment was add
ed 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 https://stackoverflow.com/questions/14039454/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.