62

I am working on ViewPager and using Fragment there I found

setUserVisibleHint() called before onCreateView() in Fragment

I am using Fragment from support library android.support.v4.app.Fragment

Is this is a problem with Library ?

How can I get rid of it ?

EDIT

I Override setUserVisibleHint() and not calling super to get rid of it.

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    //FIXED: setUserVisibleHint() called before onCreateView() in Fragment causes NullPointerException
    //super.setUserVisibleHint(isVisibleToUser);
}
Amit Yadav
  • 32,664
  • 6
  • 42
  • 57
  • 1
    I had this problem 2 hours ago too, what you want to do? – Shayan Pourvatan Jun 11 '14 at 10:57
  • @shayanpourvatan we know that our view get inflated in onCreateView() later we use it; same thing I am creating Custom Veiw in onCreateView() and later have to use it in setUserVisibleHint() TRUE – Amit Yadav Jun 11 '14 at 11:05
  • 1
    I use that method for refreshing data on fragment. if you need update or show your data you need check `getView()` on `setUserVisibleHint`, if is null, so `onCreateView` not called yet, and you can use that method from `onCreateView();` for first lunch or jumping some fragment, then change one Boolean value to Prevent duplicate calling method, i Hope this useful for you ( put refresh method in `onCreateView` and `setUserVisibleHint` ) – Shayan Pourvatan Jun 11 '14 at 11:11
  • @AmitYadav have u got solution for ur problem..I am also facing similar problem mentioned here http://stackoverflow.com/q/25200404/2624806 – CoDe Aug 11 '14 at 06:26
  • Seems like this is happening on Marshmallow and up. – AsafK Aug 03 '16 at 07:16
  • is this a google bug? its still happening.. why is that? i know how to implement workarounds, but do you have an idea why this is happening? – Martin Mlostek Feb 08 '18 at 16:32

14 Answers14

96
// create boolean for fetching data
private boolean isViewShown = false;

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    if (getView() != null) {
        isViewShown = true;
        // fetchdata() contains logic to show data when page is selected mostly asynctask to fill the data
        fetchData();
    } else {
        isViewShown = false;
    }
} 

Use isViewShown instance variable to decide whether to fetch data in onCreateView() or in setUserVisibleHint().

Below code contains logic for onCreateView():

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

   // view initialization steps.......

   if (!isViewShown) {
        fetchData();
   } 
   // do other stuff
}

This code will solve your problem. As It solved my problem. :)

This trick will fetch data in onCreateView() for direct jumping from one page to another, whereas when you swipe the view it will fetch the data from setUserVisibleHint() method. :)

ahmed hamdy
  • 5,096
  • 1
  • 47
  • 58
vikoo
  • 2,451
  • 20
  • 27
  • But what does getView() do ? – Srikanth P Feb 06 '15 at 10:46
  • ensures the view is not null so you wont get NPE – Muli Yulzary Mar 10 '15 at 22:26
  • 5
    @vikoo Do you suggest that we are not supposed to check if `isVisibleToUser` is true or false??? In your first example Why didn't you check if `isVisibleToUser` is true then check if `getView()` is null.. – Karue Benson Karue May 15 '16 at 19:35
  • 3
    The isVisibleToUser flag is not trustworty in our app. We observer it becomes (sometimes, at least) true before the view is visible. I had to create a local flag as in this solution. Recommended. – carl Nov 24 '16 at 09:05
  • 1
    setUserVisibleHint works before onCreateView, this makes your answer wrong – Murat Apr 14 '17 at 08:34
  • Direct jumping is okay. But, this approach loads Data before fragment visible. Executing network call before arrived to respective fragment. Recommend this answer-> https://stackoverflow.com/a/32223340/8035209 – ZarNi Myo Sett Win May 07 '19 at 03:22
33

you can use this logic, also you can turn off viewDidAppear any time by setting isVisible = false

public class MyFragment extends Fragment {
    private Boolean isStarted = false;
    private Boolean isVisible = false;

    @Override
    public void onStart() {
        super.onStart();
        isStarted = true;
        if (isVisible && isStarted){
            viewDidAppear();
        }
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        isVisible = isVisibleToUser;
        if (isStarted && isVisible) {
            viewDidAppear();
        }
    }

    public void viewDidAppear() {
       // your logic
    }
}
Fareed Alnamrouti
  • 30,771
  • 4
  • 85
  • 76
15

I found the best solution

private boolean isVisible;
private boolean isStarted;

@Override
public void onStart() {
    super.onStart();
    isStarted = true;
    if (isVisible)
        sendRequest(); //your request method
}

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

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    isVisible = isVisibleToUser;
    if (isVisible && isStarted)
        sendRequest(); //your request method
}

It's improved version of fareed namrouti's answer. I tested this on many conditions. It's safe.

DrMorteza
  • 2,067
  • 2
  • 21
  • 29
  • 1
    This works perfectly. I tested it too on viewpager. +1 I am confused little bit why we can't call onResume from parent fragment. – Pratik Saluja Dec 23 '17 at 11:20
5
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment, container, false);

    if (getUserVisibleHint()) {
         sendRequest();
    }

    return view;
}

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    if (isVisibleToUser) {
        if (isResumed()){ 
             sendRequest();
          }
    }
}
Gev Kostanyan
  • 51
  • 1
  • 1
3

Below Worked for me....

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState)
{
    //// create class member variable to store view
    viewFrag =inflater.inflate(R.layout.fragment_main_favorite, container, false);

    // Inflate the layout for this fragment
    return viewFrag;
}

and use this

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


            if (visible)
            {

                View v =  viewFrag ;
                if (v == null) {
                    Toast.makeText(getActivity(), "ERROR ", Toast.LENGTH_LONG ).show();
                    return;
                }
            }

    }
Vijay
  • 2,021
  • 4
  • 24
  • 33
1

My SightFragment.java here, should reset the flags in onDestroyView():

package cc.cubone.turbo.core.app;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.View;

/**
 * Fragment for handling view after it has been created and visible to user for the first time.
 *
 * <p>Specially in {@link android.support.v4.view.ViewPager}, the page will be created beforehand
 * but not be visible to user.
 *
 * <p>Call {@link android.support.v4.view.ViewPager#setOffscreenPageLimit(int)} to set the number of
 * pages that should be retained.
 *
 * Reference:
 * <ul>
 * <li><a href="http://stackoverflow.com/questions/10024739/how-to-determine-when-fragment-becomes-visible-in-viewpager">
 * How to determine when Fragment becomes visible in ViewPager</a>
 * </ul>
 */
public class SightFragment extends Fragment {

    private boolean mUserSeen = false;
    private boolean mViewCreated = false;

    public SightFragment() {
    }

    /*public boolean isUserSeen() {
        return mUserSeen;
    }

    public boolean isViewCreated() {
        return mViewCreated;
    }*/

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (!mUserSeen && isVisibleToUser) {
            mUserSeen = true;
            onUserFirstSight();
            tryViewCreatedFirstSight();
        }
        onUserVisibleChanged(isVisibleToUser);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        // Override this if you want to get savedInstanceState.
        mViewCreated = true;
        tryViewCreatedFirstSight();
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        mViewCreated = false;
        mUserSeen = false;
    }

    private void tryViewCreatedFirstSight() {
        if (mUserSeen && mViewCreated) {
            onViewCreatedFirstSight(getView());
        }
    }

    /**
     * Called when the new created view is visible to user for the first time.
     */
    protected void onViewCreatedFirstSight(View view) {
        // handling here
    }

    /**
     * Called when the fragment's UI is visible to user for the first time.
     *
     * <p>However, the view may not be created currently if in {@link android.support.v4.view.ViewPager}.
     */
    protected void onUserFirstSight() {
    }

    /**
     * Called when the visible state to user has been changed.
     */
    protected void onUserVisibleChanged(boolean visible) {
    }

}
kuokuo
  • 196
  • 1
  • 4
1

While most of this solutions work, you don't even need to track the state by yourself.

With current versions fo the support library there is a isResumed() method which does probably what most of you try to achieve by using an isStarted flag:

Return true if the fragment is in the resumed state. This is true for the duration of onResume() and onPause() as well.

And then it's as easy as:

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (isResumed()) {
            updateUi(isVisibleToUser);
        }
    }
Tobias
  • 7,282
  • 6
  • 63
  • 85
1

That simple variant work in my code:

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

    if (getUserVisibleHint()) {
        updateUI(); // your logic
    }
}

and

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

    if (isResumed() && isVisibleToUser) {
       updateUI(); // your logic
    }
}
V.March
  • 1,820
  • 1
  • 21
  • 30
0

BELOW WORKED FOR ME

Please create a global view like this

private View view; 

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        //Inflate view layout
        view =inflater.inflate(R.layout.your_fragment, container, false);

        // return view
        return view;
    }

and use this

@Override
    public void setUserVisibleHint(boolean isUserVisible)
    {
        super.setUserVisibleHint(isUserVisible);
       //When fragment is visible to user and view is not null then enter here.
            if (isUserVisible && view != null)
            {
               // do your stuff here.
            }
    }
Rahul
  • 3,293
  • 2
  • 31
  • 43
0

Create this code in your setUserVisibleHint() :

if(isVisibleToUser && getView() != null){
        isActive = true;
        init();
    }else if(isVisibleToUser && getView() == null){
        isActive = false;
    }else{
        isActive = true;
    }

In your onCreateView() :

public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
if(!isActive){
        init();
    }
}
reed
  • 47
  • 8
0

The behavior that you are experiencing is specified in the documentation itself.

Note: This method may be called outside of the fragment lifecycle and thus has no ordering guarantees with regard to fragment lifecycle method calls.

Check it here. https://developer.android.com/reference/android/app/Fragment#setUserVisibleHint(boolean)

Abhishek Luthra
  • 2,575
  • 1
  • 18
  • 34
0

Now, You no longer required to use this Method for FragmentPagerAdapter. It use send true for onView Screen and false for non-View Screen, while called for ViewPager Everytime.

Since Android have release LifeCycle in AndroidX, all lifecycle methods are called for visible screen. LifeCycle dont run after onCreateView for Non-Visible Screens in PagerAdapter.

This is for: ~Depricated setuservisiblehint

0

is hard to maintain and listen to a state, I use the hacky method to listen to visibility

For AndroidX Fragment

Initially, I put every show transaction, I added

fragment.userVisibleHint = true
currentFragment?.userVisibleHint = false
transaction.add(fragmentContainer.id, fragment, "tag").commit()

For hide/remove transactions, I added

currentFragment.userVisibleHint = false

In My Fragment/BaseFragment:

    class ExampleFragment:Fragment(){
        val visibilityStateChanges = MutableLiveData<Boolean>()
    
    override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?,
        ): View? {
    
            visibilityStateChanges.value = true
            //return your view
        }
    
        override fun setUserVisibleHint(isVisibleToUser: Boolean) {
            super.setUserVisibleHint(isVisibleToUser)
            Log.e("JeyK", "isVisibleToUser $isVisibleToUser")
            visibilityStateChanges.value = isVisibleToUser
        }
    }
}

in your Fragment, you could observe the liveData to receive visibility states

visibilityStateChanges.observe(this@MapPathFinderFragment) {
            Log.e("visibilityStateChanges", "Visibility changed $it")
        }
-1

This is the best solution i found.

    @Override
    public void onCreateView() {
        super.onStart();
        if (getUserVisibilityHint()){
            //do stuff
        }
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (isResumed() && isVisibleToUser) {
            //do stuff
        }
    }
hushed_voice
  • 3,161
  • 3
  • 34
  • 66