How to test if a fragment view is visible to the user? - android

I have a ViewPager, each page is a Fragment view. I want to test if a fragment is in a visible region. the Fragment.isVisible only test
the fragment is attached to a activity
the fragment is set to visible
the fragment has been added to a view
The ViewPager will create 3 (by default) fragment and all three of them meet the above criteria, but only one is actually visible to the user (the human eyes)

This is what I use to determine the visibility of a fragment.
private static boolean m_iAmVisible;
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
m_iAmVisible = isVisibleToUser;
if (m_iAmVisible) {
Log.d(localTAG, "this fragment is now visible");
} else {
Log.d(localTAG, "this fragment is now invisible");
}
}

You're right there is a better way to do this!
Have a look at the FragmentPagerAdapter javadoc online and you'll see there is a method setPrimaryItem(ViewGroup container, int position, Object object):void doing exactly what you need.
From the javadoc
public void setPrimaryItem (ViewGroup container, int position, Object object)
Called to inform the adapter of which item is currently considered to
be the "primary", that is the one show to the user as the current
page.
Parameters container The containing View from which the page will be
removed. position The page position that is now the primary.
object The same object that was returned by instantiateItem(View,
int).
Note on scroll state
Now if you implement this and start debugging to get a feel of when exactly this is called you'll quickly notice this is triggered several times on preparing the fragment and while the user is swiping along.
So it might be a good idea to also attach a ViewPager.OnPageChangeListener and only do what has to be done once the viewpagers scroll state becomes SCOLL_STATE_IDLE again.

For my purposes, it worked to use ViewPager.OnPageChangeListener.onPageSelected() in conjunction with Fragment.onActivityCreated() to perform an action when the Fragment is visible. Fragment.getUserVisibleHint() helps too.

I'm using "setMenuVisibility"-Method for resolving this Problem. As every Fragment can have actionbar-items this is the part where you can determine which Fragment is currently visible to the user.
#Override
public void setMenuVisibility(final boolean visible) {
super.setMenuVisibility(visible);
if (!visible) {
//not visible anymore
}else{
yay visible to the user
}
}

What is wrong with using getView().isShown() to find out if a Fragment is actually visible?

isVisible()
Can still return true even if the fragment is behind an activity.
I'm using the following:
if (getView() != null && getView().isShown()) {
//your code here
}

If you know what "page" each fragment is attached to you could use ViewPager.getCurrentItem() to determine which fragment is "visible".

In my case i a have to do some work on the first fragment when the fragment is visible to the user
#Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if(viewPager.getAdapter() instanceof YourPager)
{
Fragment fragemnt=((YourPager)viewPager.getAdapter()).getFragment(0); //getFragment(int index) custom method
if( fragemnt instanceof YourFragment)
{
((YourFragment)fragemnt).methodWhochShouldBeCalledAfterUIVisible();
}
}
}

setUserVisibleHint probably may not be called, onHiddenChanged may be called not every time when another fragment is being closed. So, you may rely on onResume (and onPause), but it is usually called too often (for example, when you turn on a device screen). Also in some situations it is not called, you should manage current fragment in host activity and write:
if (currentFragment != null) {
currentFragment.onResume();
}

Kotlin:
if (userVisibleHint) {
// the fragment is visible
} else {
// the fragment is not visible
}
Java
if (getUserVisibleHint()) {
// the fragment is visible
} else {
// the fragment is not visible
}
https://developer.android.com/reference/android/app/Fragment.html#getUserVisibleHint()
https://stackoverflow.com/a/12523627/2069407

Related

How to know when fragment actually visible in viewpager

I am using 4 fragments inside a ViewPager ,as ViewPager load the previous and next fragment in advance ,and no lifecycle method is called when navigating between fragments.
So is there any way to detect when Fragment is actually visible.
Thanks in Advance.
as per #Matt's answer setUserVisibleHint is deprecated
so here is alternative way for this.
#Override
public void setMenuVisibility(boolean isvisible) {
super.setMenuVisibility(isvisible);
if (isvisible){
Log.d("Viewpager", "fragment is visible ");
}else {
Log.d("Viewpager", "fragment is not visible ");
}
}
Of course. Assuming that viewPager is your instance of the ViewPager, use: viewPager.getCurrentItem().
Within your Fragment you can check if its instance is visible to the user like so:
#Override
public void setUserVisibleHint(boolean visible) {
super.setUserVisibleHint(visible);
if (visible) {
Log.i("Tag", "Reload fragment");
}
}
Always make sure that you search for answers throughly before asking your question. For instance, the first place you should check would be: https://developer.android.com/reference/android/support/v4/view/ViewPager.html
You can use viewPager.getCurrrentItem() to get the currently selected index, and from that you should be able to extrapolate which fragment is shown. However what you probably want is to use addOnPageChangeListener() to add an OnPageChangeListener. This will let you keep track of what page is selected, as it's selected by implementing the onPageSelected(int selected) method.
Did you try the isVisible method in the fragment?
Nowadays you can override androidx.fragment.app.onResume and androidx.fragment.app.onPauseto detect if it is visible or not respectively.

Save Data when ViewPager page changes

I'm trying to save data a user enters in a fragment to a file.
Scenario:
one viewpager and 7 fragments
A user starts in fragment 0 and can enter text into edittexts,
by swiping, using tabhost or pressing floating arrows the user can switch to other fragments.
I want to save alle entered text of the fragment the user leaves with the methods above.
I tried a OnPageChangeListener, but there i can't get the previous tab. I logged the values of the implementation methods onPageScrolled, onPageSelected, onPageScrollStateChanged.
Non of these seem to work for my needs.
onPageScrolled is called several times and shows only the current tab until it is of screen, the offset is different and not always starts by 0.0, so i can't use this reliably.
onPageSelected is the only reliable one but only returns the new current tab
onPageScrollStateChanged has no information i could use to determine the tab
I also looked into onInterceptTouchEvent in the ViewPager but this is also some times invoked several times (for MOVE events) and does not always work for every tab.
Is there a way to get this cost efficent? I want to store the data in an encrypted file and don't want to do this several times over.
Because the suggestions didn't work for my case I came up with another idea I wan't to share with others.
First instead of focusing on the ViewPager to suite my needs I thought wouldn't it be clever to led the fragment know if its changed and handle that instead.
So I created an abstract class extending the android Fragment with a boolean attribute dataChanged which I check every time the OnPageChangeListener calls onPageSelected (iterate over all fragments in the pager).
Naturally all Fragments in the pager should extend the abstract class. Furthermore I added abstract methods save() and load() to the abstract class.
So in onPageSelected(int position), after saving all changes for all fragments, which should only be one at a time, I load the data of the now selected fragment via the position attribute.
There was but one problem. If a fragment was paused and resumed the dataChanged attribute was always true if I set it in onTextChangeListeners, because of the automatic loading of widget values that android does. So I also override onResume to set the dataChanged to false.
Also every MyFragment has to handle the dataChanged attribute in the save() and load() method.
Abstract Fragment
public abstract class MyFragment extends Fragment {
private boolean dataChanged = false;
#Override
public void onResume() {
super.onResume();
setDataChanged(false);
}
public boolean isDataChanged() {
return dataChanged;
}
public void setDataChanged(boolean dataChanged) {
this.dataChanged = dataChanged;
}
public abstract void save();
public abstract void load();
}
OnPageChangeListener of ViewPager
fragmentViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
...
#Override
public void onPageSelected(int position) {
for(Fragment f : fragments) {
if(f instanceof MyFragment && ((MyFragment)f).isDataChanged()) {
((MyFragment) f).save();
}
}
if(fragmentViewPager.getCurrentItem() == position) {
Fragment fragment = getSupportFragmentManager().findFragmentByTag("android:switcher:" + R.id.view_pager + ":" + fragmentViewPager.getCurrentItem());
if(fragment instanceof MyFragment) {
((MyFragment) fragment).load();
}
}
}
...
});

How to preserve manually set InstanceState of ViewPager Fragments (in depth explanation)?

I have a ViewPager (instantiated with FragmentStatePagerAdapter) with some Fragment attached to it.
In a specific usecase I need to reset instanceBean and UI for most of the fragments in the pager.
After some googling I have tried some solutions like this but the side effects were not easy manageable. Other solution like this doesn't match my needs.
So I decided to go straight with the manual reset of the UI and instanceBean obj like in the code below:
The code
Single fragment reset
public void initFragment() {
notaBean = new NoteFragmentTO();
fromSpinnerListener = false;
}
public void resetFragment() {
initFragment();
NoteFragment.retainInstanceState = false;
}
This is done with the following code from the parent Activity:
Fragment reset from parent
private void resetAfterSaving() {
mIndicator.setCurrentItem(POSITION_F*****);
f*****Info.resetFragment();
mIndicator.setCurrentItem(POSITION_NOTE);
noteInfo.resetFragment();
mIndicator.setCurrentItem(POSITION_M*****);
m*****Info.resetFragment();
mIndicator.setCurrentItem(POSITION_V*****);
v*****.resetFragment();
}
AfterViews method:
#AfterViews
public void afterView() {
if (mSavedInstanceState != null) {
restoreState(mSavedInstanceState);
}
NoteFragment.retainInstanceState = true;
// Inits the adapters
noteAdapter = new NoteArrayAdapter(this, noteDefaultList);
sp_viol_nota_default.setAdapter(noteAdapter);
//sp_viol_nota_default.seton
et_viol_nota.setOnFocusChangeListener(new OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean hasFocus) {
if (!hasFocus) {
String readText = et_viol_nota.getText().toString().trim();
notaBean.setNota(readText == "" ? null : readText);
}
}
});
}
OnSavedInstanceState
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelableArrayList(KEY_NOTE_D_LIST, (ArrayList<VlzAnagraficaNoteagente>) noteDefaultList);
outState.putInt(KEY_NOTE_D_POSITION, !NoteFragment.retainInstanceState ? 0 : notePosition);
notaBean.setNota(!NoteFragment.retainInstanceState ? "" : et_viol_nota.getText().toString().trim());
outState.putParcelable(NoteFragmentTO.INTENT_KEY, notaBean);
}
Why do I set every page before resetting them?
Because like explained here:
When pages are not visible to the user, their entire fragment may be destroyed, only keeping the saved state of that fragment.
and because until I don't select the relative fragment the #AfterViews method (that is everything processed right after OnCreateView of the fragment) is not executed.
This throws NullPointerException for a thousand of reason (Usually in the #AfterViews method You launch RestoreState method, initializes adapter, do UI stuff).
Setting the relative page before the reset let #AfterViews method be processed.
Before checking what would happened when rotating the device, all the fragment I need are correcly reset.
When rotating the device, the error comes out:
The views (mainly EditText) go back to their previous state BEFORE my reset.
What happens?
When switching between the page, at a certain point the page will be destroyed and OnSavedInstanceState is called everytime for each page.
I have already handled the OnSavedInstanceState (like above) that when the boolean is false saves the state like if it had just been created.
I found that until within AfterView method the EditText has its text set to blank (like I want) but going on with the debug the EditText goes back to its previous state, so at the end it will show the last text it had.
Question
How can I keep the manually set (in OnSavedInstanceState) EditText text after destroying/recreating a fragment?

Android+Robotium: is view of ViewPager visible to user

I'm using a ViewPager to display 2 Fragments as tabs. Once the according activity is loaded, both fragments are loaded immediatly, while only the first one is visible to the user.
Therefore view.isShown() is not sufficent for testing, as this method returns true for the second fragment which is not visible to the user.
ViewAsserts.assertOnScreen(decorView, view) seems to behave the same way and is therefore useless for solving this problem.
I'm aware that some similar questions have been asked, but none of their answers is satisfying my needs. So how to test this behavior (using Robotium)?
Solution:
I solved it according to Leon's suggestion by using a flag within the fragment like this:
private static boolean isVisibleToUser = false;
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
MyFragment.isVisibleToUser = isVisibleToUser;
}
public static boolean isVisibleToUser() {
return isVisibleToUser;
}
implementing it as a static method I can use it in my test this way:
assertTrue(MyFragment.isVisibleToUser());
the only drawback to this solution is that I have to implement these 2 methods in every single Fragment I want to test this way... any improvements?
You could override setUserVisibleHint inside your fragment like this:
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) {
//Fragment is selected in ViewPager
//Put your "on appear" validation/loading here
}
}
This method will fire every time you show or hide the fragment in the ViewPager.
As opposed to view.isShown() this method does take a "loaded but not visible" state into account.
use OnPageChangedListener to detect changes and maintain a reference to the currently visible fragment/page.
http://developer.android.com/reference/android/support/v4/view/ViewPager.OnPageChangeListener.html
Alternatively GetCurrentItem() may work for you as detailed here: How do you get the current page number of a ViewPager for Android?

Fragment become visible

Each time my fragment become visible to the user I want to execute a peace of code that will call a web service, fetch some data and display it on the screen. I got the web service part etc working but not sure in what event I must add my code.... I tried:
onStart
onResume
onAttach
But my code doesn't fire everytime.
Am using the Android v4 comp lib with SherlockFragment as my base class.
You can use
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) { }
else { }
}
Have a look at this
This may be very old but I found setUserVisibleHint() didn't work for many of my use cases. Instead I had to resort to a hack using the ViewTreeObserver.
Basically, after your fragment is initialised, you get a view within it and do the following:
myViewInFragment.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
myMethodWhenFragmentFirstBecomesVisible();
myViewInFragment.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
}
onCreateView()
Called Every time when you change the Fragment and new Fragment become visible..
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
Below method is used determine when Fragment becomes visible in the front of a user.
private boolean loding= false; // your boolean flage
#Override
public void setUserVisibleHint(boolean isFragmentVisible) {
super.setUserVisibleHint(true);
if (this.isVisible()) {
// we check that the fragment is becoming visible first time or not
if (isFragmentVisible && !loding) {
//Task to doing while displaying fragment in front of user
loding = true;
}
}}
onResume() is called every time your fragment becomes visible to the user. There is something else wrong with your code if it doesn't
onCreateView() is called the first time the fragment needs to draw its UI
Update: This accepted answer was working 5 years ago - it doesn't anymore

Categories

Resources