How to determine when Fragment becomes visible in ViewPager
Asked Answered
B

27

829

Problem: Fragment onResume() in ViewPager is fired before the fragment becomes actually visible.

For example, I have 2 fragments with ViewPager and FragmentPagerAdapter. The second fragment is only available for authorized users and I need to ask the user to log in when the fragment becomes visible (using an alert dialog).

BUT the ViewPager creates the second fragment when the first is visible in order to cache the second fragment and makes it visible when the user starts swiping.

So the onResume() event is fired in the second fragment long before it becomes visible. That's why I'm trying to find an event which fires when the second fragment becomes visible to show a dialog at the appropriate moment.

How can this be done?

Boehmite answered 5/4, 2012 at 7:58 Comment(16)
"i have 2 fragment with ViewPager and FragmentPagerAdapter. The second fragment can be available for authorized users only and i should ask use to login when the fragment becomes visible (alert dialog)." -- IMHO, that is awful UX. Popping up a dialog because the user swiped horizontally would cause me to give you a one-star rating on the Play Store.Predicament
is it better to just display information at TextView with "Login" button? What is your solution for that case?Boehmite
Either log them in before they get to the ViewPager, or integrate the login into the to-be-secured fragment.Predicament
In general that does not make sense. Ok, imagine the next case: data loading takes a lot of time and you have to display ProgressDialog while loading. You have to display it at moment the fragment becomes visible. The problem is to determine when the fragment becomes visible.Boehmite
You would not use a ProgressDialog with a Fragment in a ViewPager. You would use a ProgressBar widget as a placeholder for the fragment's content.Predicament
The point is that i don't know the time when i should start animation in ProgressBar or show ProgressDialog as onResume is fired long before .Boehmite
You would start animation on the ProgressBar as soon as it is created. You would hide or remove the ProgressBar when it is no longer needed.Predicament
No need to load data if it will not be displayed. This loads CPU, increases traffic and drains battery and i will set one-star rating for such app.Boehmite
"No need to load data if it will not be displayed." -- then you should not be putting it in a ViewPager. In a two-page pager, both pages will be loaded immediately, whether you like it or not. The user experience of ViewPager is supposed to be that the content is there immediately upon swiping, not some time later. That is why ViewPager initializes a page ahead of what is visible, to help ensure that user experience.Predicament
It seems that ViewPager is not flexible enough and it does not allow to turn off caching since minimum setOffscreenPageLimit is 1: #10073714. Don't see any reason for this and the expected behaviour (in case of compulsory caching) is to create fragment BUT fire fragment's onResume() when the fragment becomes visible.Boehmite
I too am looking for a way to handle this "view is visible" event. I do not want to do this from the FragmentActivity using setOnPageChangeListener - so please tell me if there is another way.Diplocardiac
@Predicament Thank you for your exploration, so what is the solution for such cases?Tobacco
A bit late, but for anyone facing the same issue, you could try FragmentViewPager library (I am the author), which deals with this issue and provides a few extra features. For a sample, check project's GitHub page or this stackoverflow answer.Andrej
can anyone tell #40149539Dearly
I handle the visible state at here: #43207543. I split the Fragment's switch type into three ways, and if nested use, handle the sub Fragment's visible state in it's parent Fragment. It work for me.Antipode
here is the best way https://mcmap.net/q/55051/-how-to-know-when-fragment-actually-visible-in-viewpagerNoddle
U
610

How to determine when Fragment becomes visible in ViewPager

You can do the following by overriding setUserVisibleHint in your Fragment:

public class MyFragment extends Fragment {
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (isVisibleToUser) {
        }
        else {
        }
    }
}
Unbowed answered 17/6, 2012 at 23:42 Comment(19)
I have 3 fragments in viewstatepager and found getUserVisibleHint() always returns true.Karonkaross
getUserVisibleHint()? My example with setsetUserVisibleHint should work fine by using the boolean paramater.Unbowed
you should override setMenuVisibility as descibed by Oasis Feng. His answer should be voted up!Hilmahilt
Actually I have tried this way before, but I found that not only the currently centered page, but also the pages before and after it got "user visible hint". So it may be suitable if your ViewPager shows more than one page at the same time (usually the case on tablet UI), but not for that shows only on page at a time.Darrendarrey
Thanks to today's Android Support Library update (rev 11), the user visible hint issue is finally fixed. It's now safe to use user visible hint for ViewPager.Darrendarrey
I haven't seen setUserVisibleHint() documented anywhere as a lifecycle event of the Fragment.Mcclurg
Looks like setUserVisibleHint is only for SupportLibrary, couldn't find it in latest SDKAnissa
when should i call this method?Cristiecristin
You know, in Java a method is identified, in part, with a name (as you used), and a CLASS. So, please, when you suggest to override a method, please, please give the class. Especially as there are two objects that may be manipulated (Fragment and ViewPager).Daffy
I have found that the setUserVisibleHint method gets called BEFORE the onCreateView gets called and this makes it difficult to track any initialization.Antagonize
@AndroidDev If you want to run some code when the hint's coming in as true, but which needs the view tree already initialized, just wrap that block of code in isResumed() to avoid an NPE. Been working fine for me.Eleaseeleatic
A bit late, but for anyone facing the same issue, you could try FragmentViewPager library (I am the author), which deals with this issue and provides a few extra features. For a sample, check project's GitHub page or this stackoverflow answer.Andrej
Doesn't get called at all.Glynis
setUserVisibleHint() is not called when your App is paused on the fragment you want and you restart it from onNewIntent()Rivard
#40149539Dearly
Its just ridiculous that there are so many different hacks for something the SDK should provide by default.Arterio
Doesn't get called for me (SL 27.1.1)Pansy
setUserVisibleHint is now DeprecatedDardanus
here is the best way https://mcmap.net/q/55051/-how-to-know-when-fragment-actually-visible-in-viewpagerNoddle
D
532

UPDATE: Android Support Library (rev 11) finally fixed the user visible hint issue, now if you use support library for fragments, then you can safely use getUserVisibleHint() or override setUserVisibleHint() to capture the changes as described by gorn's answer.

UPDATE 1 Here is one small problem with getUserVisibleHint(). This value is by default true.

// Hint provided by the app that this fragment is currently visible to the user.
boolean mUserVisibleHint = true;

So there might be a problem when you try to use it before setUserVisibleHint() was invoked. As a workaround you might set value in onCreate method like this.

public void onCreate(@Nullable Bundle savedInstanceState) {
    setUserVisibleHint(false);

The outdated answer:

In most use cases, ViewPager only show one page at a time, but the pre-cached fragments are also put to "visible" state (actually invisible) if you are using FragmentStatePagerAdapter in Android Support Library pre-r11.

I override :

public class MyFragment extends Fragment {
    @Override
    public void setMenuVisibility(final boolean visible) {
        super.setMenuVisibility(visible);
        if (visible) {
            // ...
        }
    }
   // ...
}

To capture the focus state of fragment, which I think is the most suitable state of the "visibility" you mean, since only one fragment in ViewPager can actually place its menu items together with parent activity's items.

Darrendarrey answered 21/9, 2012 at 2:56 Comment(10)
Be carefull to use getActivity(), on first start it will be null.Cenesthesia
Note that setUserVisibleHint() has always worked properly in FragmentPagerAdapter.Effluence
This solution (and also gorn's) is lagging a little bit. In cases where the viewpager is swiped quickly and mutliple times, this method will be called with a delay (when the fragment is no longer visible/invisible).Constitutionally
all above solution work once fragment visible completely..is there any way to know when it just get started to visible.Involution
I am getting true for getUserVisibleHint() when onCreateOptionsMenu is called, and when setUserVisibleHint is called, the menu has not yet been been created it seems. Ultimately I get two options Menu's added when I only want the menu from the fragment that is visible. Any suggestions in this regard?Seditious
This is just to let anyone who's thinking of using this approach know that there's a delay of almost a second before your updates show up. My app is almost done and I'm not happy with the results on the phone. For some reason, this isn't very noticeable on the emulator but on the phone is very disconcerting. To give you an example, if you have a ListView setup with a TextView that shows up when the List is empty, when you swipe to that Fragment (with the backing data list now cleared) you'll see all the previous list items first and then they'll vanish with "no data/empty text" a second later!Eleaseeleatic
A bit late, but for anyone facing the same issue, you could try FragmentViewPager library (I am the author), which deals with this issue and provides a few extra features. For a sample, check project's GitHub page or this stackoverflow answer.Andrej
Above new answer works fine but it must be noted that above answer would not work if you're working with transactions. It means the fragment which is created by a transaction, always is visible by default and that callback would be called.Prothalamium
setUserVisibleHint is now DeprecatedDardanus
here is the best way https://mcmap.net/q/55051/-how-to-know-when-fragment-actually-visible-in-viewpagerNoddle
J
147

This seems to restore the normal onResume() behavior that you would expect. It plays well with pressing the home key to leave the app and then re-entering the app. onResume() is not called twice in a row.

@Override
public void setUserVisibleHint(boolean visible)
{
    super.setUserVisibleHint(visible);
    if (visible && isResumed())
    {
        //Only manually call onResume if fragment is already visible
        //Otherwise allow natural fragment lifecycle to call onResume
        onResume();
    }
}

@Override
public void onResume()
{
    super.onResume();
    if (!getUserVisibleHint())
    {
        return;
    }

    //INSERT CUSTOM CODE HERE
}
Joijoice answered 3/8, 2014 at 3:2 Comment(10)
isResumed() was really helpful to get the desired result.Stclair
Exactly what I was looking for. Solves the problem with setUserVisibleHint being called before onCreateView and that setUserVisibleHint doesn't get called if app goes background and then foreground. Awesome! Thank you!Prothalamium
I think calling onResume yourself is a pretty bad idea, but otherwise, everything you need to answer the question of this post is in the setUserVisibleHint function !Blatherskite
Yea, I'd probably tend to agree; for this example I implemented like that only for the sake of simplicity. You can create your own method onVisible or whatever you'd like to name it and call that method instead of onResume.Joijoice
Thanks a lot, this together with some checks in addOnTabSelectedListener worked great for me.Palsgrave
I'd rather implement a plain onVisibleToUser() method and have that called from onResume() and from setUserVisibleHint(boolean) instead of calling onResume() myself and interfering with life-cycle callbacks. Otherwise I think this approach works nicely, thanks!Swarthy
Yes I agree with the above comments. In general, try to avoid calling public methods from public methods in your class. Make a private method and call it from both public methods.Wrongheaded
Thank you very much! Also who can post complete good practice code?Tarrasa
Best answer, Thank you.Kirkuk
setUserVisibleHint deprecated!Ewer
S
78

Here is another way using onPageChangeListener:

  ViewPager pager = (ViewPager) findByViewId(R.id.viewpager);
  FragmentPagerAdapter adapter = new FragmentPageAdapter(getFragmentManager);
  pager.setAdapter(adapter);
  pager.setOnPageChangeListener(new OnPageChangeListener() {

  public void onPageSelected(int pageNumber) {
    // Just define a callback method in your fragment and call it like this! 
    adapter.getItem(pageNumber).imVisible();

  }

  public void onPageScrolled(int arg0, float arg1, int arg2) {
    // TODO Auto-generated method stub

  }

  public void onPageScrollStateChanged(int arg0) {
    // TODO Auto-generated method stub

  }
});
Slowworm answered 3/8, 2012 at 14:41 Comment(8)
This solution works well, thanks. This should be the recommended solution for those building for older platforms using the (v4) fragment support packagesKyoko
It's worth noting that this could not give you the expected result if you're using a FragmentStatePagerAdapter and instantiating a new Fragment instance on getItem(), because you would only be setting the state on the new fragment and not the one that the viewpager gotParsee
This solution is good if you want to perform something when the fragment becomes visible. It doesn't keep a fragment's visible state (meaning, it doesn't know when the fragment is invisible).Constitutionally
I am same type of problem. please help me to solve the problem. My Post : #23115783Dactylo
this is giving null pointer exception for adapter which i am using in the iamVisible method of the fragment. I am trying to set data received from network only when the fragment is visible.Geographer
This call seems to be deprecated.Stearne
see #30868352Nichani
@Constitutionally If you have ViewPager in tabs for example, only one fragment would be visible at a time right? So can't we assume rest of them are hidden except the one for this the onPageSelected is called?Oakman
W
63

In ViewPager2 and ViewPager from version androidx.fragment:fragment:1.1.0 you can just use onPause and onResume callbacks to determine which fragment is currently visible for the user. onResume callback is called when fragment became visible and onPause when it stops to be visible.

In case of ViewPager2 it is default behavior but the same behavior can be enabled for old good ViewPager easily.

To enable this behavior in the first ViewPager you have to pass FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT parameter as second argument of FragmentPagerAdapter constructor.

FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)

Note: setUserVisibleHint() method and FragmentPagerAdapter constructor with one parameter are now deprecated in the new version of Fragment from android jetpack.

Wood answered 11/9, 2019 at 11:44 Comment(7)
Thanks for the nice solution. I was looking for this from long time. Great WorkGustatory
For ViewPager2 the option BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT is not the default behavior, you need to set it manually. When Setting this option your solution worked for me. Thanks.Jibheaded
As of APR 2020 this is the updated solution and works like a charm.Jedidiah
@Boehmite please consider accepting this answer as the correct answer, since as of now this is correct and most answers are using the deprecated setUserVisibleHint methodArmandoarmature
I think it works well with FragmentStateAdapter as well. That's a huge relief for any android developer. Thanks to google for solving this after so long.Bronwynbronx
this is the best solution ever as long as you can use androidx fragment 1.1.0! thanks @WoodDarken
How do we determine if this onPause is because of ViewPager swiped or a dialog opened on top?!Siltstone
F
57

setUserVisibleHint() gets called sometimes before onCreateView() and sometimes after which causes trouble.

To overcome this you need to check isResumed() as well inside setUserVisibleHint() method. But in this case i realized setUserVisibleHint() gets called only if Fragment is resumed and visible, NOT when Created.

So if you want to update something when Fragment is visible, put your update function both in onCreate() and setUserVisibleHint():

@Override
public View onCreateView(...){
    ...
    myUIUpdate();
    ...        
}
  ....
@Override
public void setUserVisibleHint(boolean visible){
    super.setUserVisibleHint(visible);
    if (visible && isResumed()){
        myUIUpdate();
    }
}

UPDATE: Still i realized myUIUpdate() gets called twice sometimes, the reason is, if you have 3 tabs and this code is on 2nd tab, when you first open 1st tab, the 2nd tab is also created even it is not visible and myUIUpdate() is called. Then when you swipe to 2nd tab, myUIUpdate() from if (visible && isResumed()) is called and as a result,myUIUpdate() may get called twice in a second.

The other problem is !visible in setUserVisibleHint gets called both 1) when you go out of fragment screen and 2) before it is created, when you switch to fragment screen first time.

Solution:

private boolean fragmentResume=false;
private boolean fragmentVisible=false;
private boolean fragmentOnCreated=false;
...

@Override
public View onCreateView(...){
    ...
    //Initialize variables
    if (!fragmentResume && fragmentVisible){   //only when first time fragment is created
        myUIUpdate();
    }
    ...        
}

@Override
public void setUserVisibleHint(boolean visible){
    super.setUserVisibleHint(visible);
    if (visible && isResumed()){   // only at fragment screen is resumed
        fragmentResume=true;
        fragmentVisible=false;
        fragmentOnCreated=true;
        myUIUpdate();
    }else  if (visible){        // only at fragment onCreated
        fragmentResume=false;
        fragmentVisible=true;
        fragmentOnCreated=true;
    }
    else if(!visible && fragmentOnCreated){// only when you go out of fragment screen
        fragmentVisible=false;
        fragmentResume=false;
    }
}

Explanation:

fragmentResume,fragmentVisible: Makes sure myUIUpdate() in onCreateView() is called only when fragment is created and visible, not on resume. It also solves problem when you are at 1st tab, 2nd tab is created even if it is not visible. This solves that and checks if fragment screen is visible when onCreate.

fragmentOnCreated: Makes sure fragment is not visible, and not called when you create fragment first time. So now this if clause only gets called when you swipe out of fragment.

Update You can put all this code in BaseFragment code like this and override method.

Franklyn answered 14/4, 2015 at 7:27 Comment(1)
Your solution works perfectly except with one scenario, when the fragment is opened normally and then you click some button to replace it with another fragment and then click back to pop the new fragment from the backstack, it that case setUserVisibleHint didn't get call..! and inside onCreateView method the fragmentVisible was false !? so the fragment showed up empty..! any thoughts.?Delectation
G
35

New

ViewPager2 + FragmentStateAdapter + onResume() (in Fragment) solve the problem

Old Answer (deprecated)

To detect Fragment in ViewPager visible, I'm quite sure that only using setUserVisibleHint is not enough.
Here is my solution to check if a fragment is visible or invisible. First when launching viewpager, switch between page, go to another activity/fragment/ background/foreground`

public class BaseFragmentHelpLoadDataWhenVisible extends Fragment {
    protected boolean mIsVisibleToUser; // you can see this variable may absolutely <=> getUserVisibleHint() but it not. Currently, after many test I find that

    /**
     * This method will be called when viewpager creates fragment and when we go to this fragment background or another activity or fragment
     * NOT called when we switch between each page in ViewPager
     */
    @Override
    public void onStart() {
        super.onStart();
        if (mIsVisibleToUser) {
            onVisible();
        }
    }

    @Override
    public void onStop() {
        super.onStop();
        if (mIsVisibleToUser) {
            onInVisible();
        }
    }

    /**
     * This method will called at first time viewpager created and when we switch between each page
     * NOT called when we go to background or another activity (fragment) when we go back
     */
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        mIsVisibleToUser = isVisibleToUser;
        if (isResumed()) { // fragment have created
            if (mIsVisibleToUser) {
                onVisible();
            } else {
                onInVisible();
            }
        }
    }

    public void onVisible() {
        Toast.makeText(getActivity(), TAG + "visible", Toast.LENGTH_SHORT).show();
    }

    public void onInVisible() {
        Toast.makeText(getActivity(), TAG + "invisible", Toast.LENGTH_SHORT).show();
    }
}

EXPLANATION You can check the logcat below carefully then I think you may know why this solution will work

First launch

Fragment1: setUserVisibleHint: isVisibleToUser=false isResumed=false
Fragment2: setUserVisibleHint: isVisibleToUser=false isResumed=false
Fragment3: setUserVisibleHint: isVisibleToUser=false isResumed=false
Fragment1: setUserVisibleHint: isVisibleToUser=true isResumed=false // AT THIS TIME isVisibleToUser=true but fragment still not created. If you do something with View here, you will receive exception
Fragment1: onCreateView
Fragment1: onStart mIsVisibleToUser=true
Fragment2: onCreateView
Fragment3: onCreateView
Fragment2: onStart mIsVisibleToUser=false
Fragment3: onStart mIsVisibleToUser=false

Go to page2

Fragment1: setUserVisibleHint: isVisibleToUser=false isResumed=true
Fragment2: setUserVisibleHint: isVisibleToUser=true isResumed=true

Go to page3

Fragment2: setUserVisibleHint: isVisibleToUser=false isResumed=true
Fragment3: setUserVisibleHint: isVisibleToUser=true isResumed=true

Go to background:

Fragment1: onStop mIsVisibleToUser=false
Fragment2: onStop mIsVisibleToUser=false
Fragment3: onStop mIsVisibleToUser=true

Go to foreground

Fragment1: onStart mIsVisibleToUser=false
Fragment2: onStart mIsVisibleToUser=false
Fragment3: onStart mIsVisibleToUser=true

DEMO project here

Hope it help

Goldbrick answered 3/2, 2017 at 8:34 Comment(2)
It not works when a fragment has another view pager which also consists fragment.Vday
sorry, I can not post the answer at this time because i don't have enough time, if possible please refer my git about your problem here github.com/PhanVanLinh/AndroidViewPagerSkeleton/tree/master. SubChildContainerFragment is used for detect a fragment has another view pager which also consists fragment. You can mix SubChildContainerFragment and ChildContainerFragment to 1 class. Hope it help. I will post the complete answer laterGoldbrick
S
26
package com.example.com.ui.fragment;


import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.example.com.R;

public class SubscribeFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_subscribe, container, false);
        return view;
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);

        if (isVisibleToUser) {
            // called here
        }
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
    }
}
Submissive answered 6/1, 2016 at 13:54 Comment(2)
This should be the accepted answer. Works with android.app.Fragment also, which is a huge bonus.Evaporimeter
setUserVisibleHint DeprecatedCoheman
B
18

Override setPrimaryItem() in the FragmentPagerAdapter subclass. I use this method, and it works well.

@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
    // This is what calls setMenuVisibility() on the fragments
    super.setPrimaryItem(container, position, object);

    if (object instanceof MyWhizBangFragment) {
        MyWhizBangFragment fragment = (MyWhizBangFragment) object;
        fragment.doTheThingYouNeedToDoOnBecomingVisible();
    }
}
Bealle answered 19/6, 2015 at 18:34 Comment(3)
This almost worked but had issues with NPE and being called multiple times. Posted an alternative below!Motor
@kris #40149539Dearly
@Motor saving Object object at first time,and check and ignore the follow up call with the same Object object just like FragmentStateAdapter do in setPrimaryItem() methodRobeson
G
16

Override Fragment.onHiddenChanged() for that.

public void onHiddenChanged(boolean hidden)

Called when the hidden state (as returned by isHidden()) of the fragment has changed. Fragments start out not hidden; this will be called whenever the fragment changes state from that.

Parameters
hidden - boolean: True if the fragment is now hidden, false if it is not visible.

Gemagemara answered 9/9, 2012 at 12:4 Comment(6)
this method is deprecated by now and can not be used anymoreDemonetize
@bluewhile where do you see it is deprecated? developer.android.com/reference/android/app/…Hagiographa
I've just used this with success, and the documentation don't seem to indicate it's deprecated.Whippersnapper
@bluewhile, in 4.1 (api 16) it is not deprecated yet.Gemagemara
I'm using API level 19 and can confirm that while this is not deprecated, it does not work as advertised. onHiddenChanged is not called when the fragment is hidden by another fragment, for example.Odetteodeum
This method is not called.Cleptomania
C
9

Only this worked for me!! and setUserVisibleHint(...) is now deprecated (I attached docs at end), which means most of other answers are deprecated ;-)

public class FragmentFirewall extends Fragment {
    /**
     * Required cause "setMenuVisibility(...)" is not guaranteed to be
     * called after "onResume()" and/or "onCreateView(...)" method.
     */
    protected void didVisibilityChange() {
        Activity activity = getActivity();
        if (isResumed() && isMenuVisible()) {
            // Once resumed and menu is visible, at last
            // our Fragment is really visible to user.
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        didVisibilityChange();
    }

    @Override
    public void setMenuVisibility(boolean visible) {
        super.setMenuVisibility(visible);
        didVisibilityChange();
    }
}

Tested and works with NaviagationDrawer as well, there isMenuVisible() will always return true (and onResume() seems enough, but we want to support ViewPager too).

setUserVisibleHint is deprecated. If overriding this method, behavior implemented when passing in true should be moved to Fragment.onResume(), and behavior implemented when passing in false should be moved to Fragment.onPause().

Caretaker answered 28/7, 2020 at 6:13 Comment(0)
C
8

setUserVisibleHint(boolean visible) is now deprecated So this is the correct solution

FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)

In ViewPager2 and ViewPager from version androidx.fragment:fragment:1.1.0 you can just use onPause() and onResume() to determine which fragment is currently visible for the user. onResume() is called when the fragment became visible and onPause when it stops to be visible.

To enable this behavior in the first ViewPager you have to pass FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT parameter as the second argument of the FragmentPagerAdapter constructor.

Crucifixion answered 6/6, 2020 at 10:6 Comment(1)
you are lifesaver mate , this is the best answer in 2021 as all the above answers are depreciated .Colloquy
R
5

I figured out that onCreateOptionsMenu and onPrepareOptionsMenu methods called only in the case of the fragment really visible. I could not found any method which behaves like these, also I tried OnPageChangeListener but it did not work for the situations, for example, I need a variable initialized in onCreate method.

So these two methods can be used for this problem as a workaround, specifically for little and short jobs.

I think, this is the better solution but not the best. I will use this but wait for better solution at the same time.

Regards.

Rania answered 24/6, 2014 at 8:20 Comment(0)
M
2

Another solution posted here overriding setPrimaryItem in the pageradapter by kris larson almost worked for me. But this method is called multiple times for each setup. Also I got NPE from views, etc. in the fragment as this is not ready the first few times this method is called. With the following changes this worked for me:

private int mCurrentPosition = -1;

@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
    super.setPrimaryItem(container, position, object);

    if (position == mCurrentPosition) {
        return;
    }

    if (object instanceof MyWhizBangFragment) {
        MyWhizBangFragment fragment = (MyWhizBangFragment) object;

        if (fragment.isResumed()) {
            mCurrentPosition = position;
            fragment.doTheThingYouNeedToDoOnBecomingVisible();
        }
    }
}
Motor answered 6/4, 2016 at 12:57 Comment(1)
Not working. It is calling only once. If again comeback to the same page, the mCurrentPosition and position are equalWanda
I
2

I encountered the same problem while working with FragmentStatePagerAdapters and 3 tabs. I had to show a Dilaog whenever the 1st tab was clicked and hide it on clicking other tabs.

Overriding setUserVisibleHint() alone didn't help to find the current visible fragment.

When clicking from 3rd tab -----> 1st tab. It triggered twice for 2nd fragment and for 1st fragment. I combined it with isResumed() method.

    @Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    isVisible = isVisibleToUser;

    // Make sure that fragment is currently visible
    if (!isVisible && isResumed()) {
        // Call code when Fragment not visible
    } else if (isVisible && isResumed()) {
       // Call code when Fragment becomes visible.
    }

}
Insight answered 11/11, 2016 at 19:37 Comment(0)
C
2

Add following Code inside fragment

@Override
public void setMenuVisibility(final boolean visible) 
 {
    super.setMenuVisibility(visible);
    if (visible && isResumed()) 
     {

     }
}
Cataplexy answered 2/1, 2017 at 18:24 Comment(1)
This only works for ViewPagerFragments. Not for fragments in normal activity.Cleptomania
W
2

We have a special case with MVP where the fragment needs to notify the presenter that the view has become visible, and the presenter is injected by Dagger in fragment.onAttach().

setUserVisibleHint() is not enough, we've detected 3 different cases that needed to be addressed (onAttach() is mentioned so that you know when the presenter is available):

  1. Fragment has just been created. The system makes the following calls:

    setUserVisibleHint() // before fragment's lifecycle calls, so presenter is null
    onAttach()
    ...
    onResume()
    
  2. Fragment already created and home button is pressed. When restoring the app to foreground, this is called:

    onResume()
    
  3. Orientation change:

    onAttach() // presenter available
    onResume()
    setUserVisibleHint()
    

We only want the visibility hint to get to the presenter once, so this is how we do it:

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View root = inflater.inflate(R.layout.fragment_list, container, false);
    setHasOptionsMenu(true);

    if (savedInstanceState != null) {
        lastOrientation = savedInstanceState.getInt(STATE_LAST_ORIENTATION,
              getResources().getConfiguration().orientation);
    } else {
        lastOrientation = getResources().getConfiguration().orientation;
    }

    return root;
}

@Override
public void onResume() {
    super.onResume();
    presenter.onResume();

    int orientation = getResources().getConfiguration().orientation;
    if (orientation == lastOrientation) {
        if (getUserVisibleHint()) {
            presenter.onViewBecomesVisible();
        }
    }
    lastOrientation = orientation;
}

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    if (presenter != null && isResumed() && isVisibleToUser) {
        presenter.onViewBecomesVisible();
    }
}

@Override public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt(STATE_LAST_ORIENTATION, lastOrientation);
}
Warfold answered 8/5, 2017 at 15:30 Comment(0)
I
2

Detecting by focused view!

This works for me

public static boolean isFragmentVisible(Fragment fragment) {
    Activity activity = fragment.getActivity();
    View focusedView = fragment.getView().findFocus();
    return activity != null
            && focusedView != null
            && focusedView == activity.getWindow().getDecorView().findFocus();
}
Iowa answered 16/12, 2017 at 11:43 Comment(2)
focusedView returning null.Variegated
@Variegated Yeah I realized that there are some different situations which focusedView will be null. I'll try to find a new way.Iowa
A
1

I had the same issue. ViewPager executes other fragment life cycle events and I could not change that behavior. I wrote a simple pager using fragments and available animations. SimplePager

Adage answered 11/3, 2014 at 18:35 Comment(0)
I
1

I used this and it worked !

mContext.getWindow().getDecorView().isShown() //boolean
Inquire answered 21/9, 2018 at 10:57 Comment(0)
C
1

I support SectionsPagerAdapter with child fragments so after a lot of headache I finally got working version based on solutions from this topic:

public abstract class BaseFragment extends Fragment {

    private boolean visible;
    private boolean visibilityHintChanged;

    /**
     * Called when the visibility of the fragment changed
     */
    protected void onVisibilityChanged(View view, boolean visible) {

    }

    private void triggerVisibilityChangedIfNeeded(boolean visible) {
        if (this.visible == visible || getActivity() == null || getView() == null) {
            return;
        }
        this.visible = visible;
        onVisibilityChanged(getView(), visible);
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (!visibilityHintChanged) {
            setUserVisibleHint(false);
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        if (getUserVisibleHint() && !isHidden()) {
            triggerVisibilityChangedIfNeeded(true);
        }
    }

    @Override
    public void onHiddenChanged(boolean hidden) {
        super.onHiddenChanged(hidden);
        triggerVisibilityChangedIfNeeded(!hidden);
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        visibilityHintChanged = true;
        if (isVisibleToUser && isResumed() && !isHidden()) {
            triggerVisibilityChangedIfNeeded(true);
        } else if (!isVisibleToUser) {
            triggerVisibilityChangedIfNeeded(false);
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        triggerVisibilityChangedIfNeeded(false);
    }

    @Override
    public void onStop() {
        super.onStop();
        triggerVisibilityChangedIfNeeded(false);
    }

    protected boolean isReallyVisible() {
        return visible;
    }
}
Corky answered 9/5, 2019 at 9:56 Comment(2)
Forgot to mention, once you add child fragment to parent fragment set fragment.setUserVisibleHint(true);Corky
And don't forget to use getChildFragmentManager() instead of getFragmentManager() for adding child fragments.Corky
S
0

I encountered this problem when I was trying to get a timer to fire when the fragment in the viewpager was on-screen for the user to see.

The timer always started just before the fragment was seen by the user. This is because the onResume() method in the fragment is called before we can see the fragment.

My solution was to do a check in the onResume() method. I wanted to call a certain method 'foo()' when fragment 8 was the view pagers current fragment.

@Override
public void onResume() {
    super.onResume();
    if(viewPager.getCurrentItem() == 8){
        foo();
        //Your code here. Executed when fragment is seen by user.
    }
}

Hope this helps. I've seen this problem pop up a lot. This seems to be the simplest solution I've seen. A lot of others are not compatible with lower APIs etc.

Span answered 5/8, 2015 at 14:12 Comment(1)
I downvoted this answer because it uses a "magic number," which means it only works for Piyush's app.Wynd
P
0

Note that setUserVisibleHint(false) is not called on activity / fragment stop. You'll still need to check start/stop to properly register/unregister any listeners/etc.

Also, you'll get setUserVisibleHint(false) if your fragment starts in a non-visible state; you don't want to unregister there since you've never registered before in that case.

@Override
public void onStart() {
    super.onStart();

    if (getUserVisibleHint()) {
        // register
    }
}

@Override
public void onStop() {
    if (getUserVisibleHint()) {
        // unregister
    }

    super.onStop();
}

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);

    if (isVisibleToUser && isResumed()) {
        // register

        if (!mHasBeenVisible) {
            mHasBeenVisible = true;
        }
    } else if (mHasBeenVisible){
        // unregister
    }
}
Perrine answered 9/12, 2015 at 19:53 Comment(0)
C
0

A simple way of implementing that is checking whether user is logged in before going to the fragment.

In your MainActivity you may do something like this inside the onNavigationItemSelected method.

 case R.id.nav_profile_side:


                if (User_is_logged_in) {

                    fragmentManager.beginTransaction()
                            .replace(R.id.content_frame
                                    , new FragmentProfile())
                            .commit();
                }else {

                    ShowLoginOrRegisterDialog(fragmentManager);

                }

                break;

However, if you are using navigation drawer, the selection in the drawer will have changed to Profile though we have not gone to the ProfileFragment.

To reset the selection to the current selection run the code below

        navigationView.getMenu().getItem(0).setChecked(true);
Concepcion answered 28/5, 2019 at 17:35 Comment(0)
W
0

May be very late. This is working for me. I slightly updated the code from @Gobar and @kris Solutions. We have to update the code in our PagerAdapter.

setPrimaryItem is called every time when a tab is visible and returns its position. If the position are same means we are unmoved. If position changed and current position is not our clicked tab set as -1.

private int mCurrentPosition = -1;

@Override
public void setPrimaryItem(@NotNull ViewGroup container, int position, @NotNull Object object) {
    // This is what calls setMenuVisibility() on the fragments
    super.setPrimaryItem(container, position, object);
    if (position == mCurrentPosition) {
        return;
    }
    if (object instanceof YourFragment) {
        YourFragment fragment = (YourFragment) object;
        if (fragment.isResumed()) {
            mCurrentPosition = position;
            fragment.doYourWork();//Update your function
        }
    } else {
        mCurrentPosition = -1;
    }
}
Wanda answered 24/9, 2020 at 6:46 Comment(0)
A
0

in Kotlin

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

    // Your code goes here..
}
Antiperiodic answered 14/8, 2022 at 11:38 Comment(0)
B
-4

I overrode the Count method of the associated FragmentStatePagerAdapter and have it return the total count minus the number of pages to hide:

 public class MyAdapter : Android.Support.V13.App.FragmentStatePagerAdapter
 {   
     private List<Fragment> _fragments;

     public int TrimmedPages { get; set; }

     public MyAdapter(Android.App.FragmentManager fm) : base(fm) { }

     public MyAdapter(Android.App.FragmentManager fm, List<Android.App.Fragment> fragments) : base(fm)
     {
         _fragments = fragments;

         TrimmedPages = 0;
     }

     public override int Count
     {
         //get { return _fragments.Count; }
         get { return _fragments.Count - TrimmedPages; }
     }
 }

So, if there are 3 fragments initially added to the ViewPager, and only the first 2 should be shown until some condition is met, override the page count by setting TrimmedPages to 1 and it should only show the first two pages.

This works good for pages on the end, but wont really help for ones on the beginning or middle (though there are plenty of ways of doing this).

Bremsstrahlung answered 11/1, 2013 at 21:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.