16

How could some part of my code be aware of Fragment instance become visible on a screen?

The following snippet will explain my question.

public class MyApp extends Application {
public static final String TAG = MyApp.class.getSimpleName();

@Override
public void onCreate() {
    super.onCreate();
    registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
        ...

        @Override
        public void onActivityResumed(Activity activity) {
           Log.d(TAG, activity.getClass().getSimpleName() + " is on screen");
        }

        @Override
        public void onActivityStopped(Activity activity) {
            Log.d(TAG, activity.getClass().getSimpleName() + " is NOT on screen");
        }

        ...
    });
}

Here i can track when any activity within my app appears on the screen. Is there any way to extend this approach on Fragments?

Something like Activity.getFragmentManager().registerFragmentLifecycleCallbacks();

UPD. I know nothing about activities implementations, do they use fragments at all and how do they use them (injection via xml, ViewPager etc.) The only thing I have within my class is an application context. Let's assume Activity and Fragment implementations are black boxes and i am not able to make any changes.

Johnny Doe
  • 3,280
  • 4
  • 29
  • 45
  • if your Fragment's onResume method get called that means your fragment is visible for the user. – Toppers Apr 06 '15 at 11:36
  • Well... Let's say Fragment implementation is a black box and i have no ability to add few lines of code. – Johnny Doe Apr 06 '15 at 12:57
  • So your the one pushing the unresponsive app design. – danny117 Apr 14 '15 at 16:31
  • @danny117 i am not sure i understand what did you mean about "unresponsive app design" – Johnny Doe Apr 16 '15 at 19:15
  • Unresponsive absolutely because your going to do something every single time the fragment just comes into view my tired old phone will feel unresponsive while your code is running. If its an animation or similar then I will enjoy it. But If the fragment uses a data connection to update the UI I'll be FTLOA I told you that is an unresponsive app design. – danny117 Apr 16 '15 at 19:29
  • @danny117 But i am just printing into the logcat(or switching boolean flag), no more. Given just that. How to do a work with minimal effect on UI - it is question about multithreading, handlers, algorithms optimization etc. – Johnny Doe Apr 16 '15 at 19:54

14 Answers14

7

In your fragment, override onHiddenChanged(...) method:

@Override
public void onHiddenChanged(boolean hidden) {
    super.onHiddenChanged(hidden);
    if (hidden) {
        Log.d(TAG, ((Object) this).getClass().getSimpleName() + " is NOT on screen");        
    } 
    else 
    {
        Log.d(TAG, ((Object) this).getClass().getSimpleName() + " is on screen");
    }
}

Hope this work for you!

Xcihnegn
  • 11,579
  • 10
  • 33
  • 33
  • 2
    I think this method won't be called if the removed fragment was added (and not replaced) by FragmentTransaction. For Example: If you have two fragments: fragmentA and fragmentB. Currently fragmentA is visible on the screen, and you add fragmentB, with ft.add(..., fragmentB, ...); after you backPressed, the onHiddenChanged won't be called in fragmentA, because it was not hidden. – Hanoch Moreno May 21 '20 at 19:08
7

Without touching the Activity or Fragment code and assuming you don't know the tag or layout it is placed in, there is very little that you can do. The best that I can see is that you could get the FragmentManager in ActivityResumed and ActivityStopped callbacks (because here you have an Activity reference) and apply a BackstackChangedListener. This assumes that you use the backstack when changing between fragments.

The issue with what you are asking is that you want lifecycle callbacks for Fragments on the Application level when you have no control over the middle men, the Activities which are already starved for Fragment callbacks. They do most everything through their FragmentManager, and propagate their own lifecycle callbacks down to the Fragments so that the fragments will behave appropriately. The onResume and onPause callbacks in fragments only occur when they are first created or when the Activity experiences those callbacks. There is only one lifecycle callback for Fragments in Activities, onAttachFragment, which if you could override, would give you references to the Fragments that are attached to the Activity. But you said you can't change the Activity or the Fragment, and you want to know when the Fragments are shown.

So if you don't use the backstack, I don't think there's a way to do what you want.

Joey Harwood
  • 961
  • 1
  • 17
  • 27
4

For putting Fragments inside Activity i use SlidingTabLayout which Google uses. Inside it you have ViewPager and some Adapter to populate many Fragments. First of all you have to put this and this files in your project. Then here there is good tutorial for how you can implement SlidingTabLayout.

1) After you have implemented SlidingTabLayout in your Activity, you can detect when and which Fragment becomes visible from Activity:

  mSlidingTabLayout.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
             //Do nothing
        }
        @Override
        public void onPageSelected(int position) {
            if (position == 0) {
                //Whenever first fragment is visible, do something
            } else if (position == 1) {
               //Whenever second fragment is visible, do something
            } else if (position == 2) {
               //Whenever third fragment is visible, do something
            } else if (position == 3) {
                //Whenever fourth fragment is visible, do something
            }
        }
        @Override
        public void onPageScrollStateChanged(int state) {
            //Do nothing
        }
    });

2) You can detect if Fragment is visible from Fragment itself as i answered here, however this may get called before onCreateView() of Fragment, so check answer in the link:

@Override
public void setUserVisibleHint(boolean visible){
    super.setUserVisibleHint(visible);
    if (visible){
        //when this Fragment is active, do something
    }
}

3) You can change also change colors of indicators of each Tab like this from Activity:

    mSlidingTabLayout.setCustomTabColorizer(new SlidingTabLayout.TabColorizer() {
        @Override
        public int getIndicatorColor(int position) {
            if (position == 0) {
                return getResources().getColor(R.color.orange);
            } else if (position == 1) {
                return getResources().getColor(R.color.redDimmed);
            } else if (position == 2) {
                return getResources().getColor(R.color.yellow);
            } else if (position == 3) {
                return getResources().getColor(R.color.green);
            } else {
                return getResources().getColor(R.color.redLight);
            }
        }

        @Override
        public int getDividerColor(int position) {
            return getResources().getColor(R.color.defaultActionBarBg);
        }

    });
Community
  • 1
  • 1
Jemshit
  • 9,501
  • 5
  • 69
  • 106
1

Use same way as activity

set flag in application class to check visiblity of fragment, use below code in fragment

    @Override
        public void onStart() {
            super.onStart();
           Log.e( "Fragment is visible", "Fragment is visible");
Application Class.isFragmentShow = true;
        }

    @Override
        public void onPause() {
            super.onPause();
            Log.e("Fragment is not visible", "Fragment is not visible");

Application Class.isFragmentShow = false;
        }

to communicate with fragment you have to call that activity in which fragment added then use below code

MainFragment fragment = (MainFragment) fragmentManager.findFragmentByTag("MainFragment");
                    fragment.setFilter();
Dhaval Parmar
  • 18,812
  • 8
  • 82
  • 177
  • Unfortunately, I can't perform any modifications of fragments :(. Moreover I know anything about are they present, their names, transaction tags, etc... The only solution i see at the moment is to create a bunch of `TrackableFragment` classes and to obligate developers extend them. This is the most straightforward and obvious way, but not the most elegant one. – Johnny Doe Apr 15 '15 at 12:15
1

Don't exist a default way to do, but you can make your own Callbacks, I made this and works fine, first need have a BaseFragment class where we'll handle all fragment events.

public class BaseFragment extends Fragment {

private String fragmentName;
private FragmentLifecycleCallbacks listener;

public void registerCallBacks(String fragmentName){
    // handle the listener that implement 'MyApp' class
try{
    listener = (FragmentLifecycleCallbacks) getActivity().getApplication();
   } catch (ClassCastException e) {
        throw new ClassCastException("Application class must implement FragmentLifecycleCallbacks");
    }
    // set the current fragment Name for the log
    this.fragmentName = fragmentName;
}

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    if(listener!=null) {
        listener.onAttachFragment(fragmentName);
    }
}

@Override
public void onResume() {
    super.onResume();
    if(listener!=null) {
        listener.onResumeFragment(fragmentName);
    }
}

@Override
public void onStop() {
    super.onStop();
    if(listener!=null) {
        listener.onStopFragment(fragmentName);
    }
}

// 'MyApp' class needs implement this interface to handle all the fragments events
public interface FragmentLifecycleCallbacks{
    void onStopFragment(String fragmentName);
    void onResumeFragment(String fragmentName);
    void onAttachFragment(String fragmentName);
}}

On 'MyApp' class implement the interface of BaseFragment

public class MyApp extends Application implements BaseFragment.FragmentLifecycleCallbacks{

public static final String TAG = MyApp.class.getSimpleName();

@Override
public void onCreate() {
    super.onCreate();
}


@Override
public void onStopFragment(String fragmentName) {
    Log.d(TAG, fragmentName + " is NOT on screen");
}

@Override
public void onResumeFragment(String fragmentName) {
    Log.d(TAG, fragmentName + " is on screen");
}

@Override
public void onAttachFragment(String fragmentName) {
    Log.d(TAG, fragmentName + " is attached to screen");
}}

And now each Fragment that you have need extends 'BaseFragment' and register to the global listener

public class FragmentA extends BaseFragment {

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {

    View rootView = inflater.inflate(R.layout.fragment_simple, container, false);
    // here register to the global listener
    registerCallBacks(FragmentA.class.getName());

    return rootView;
}}

Hope this helps!

Vinicius DSL
  • 1,839
  • 1
  • 18
  • 26
0

Intercept onWindowFocusChanged() in the activity and propagate that to the interested fragment.

Jose L Ugia
  • 5,960
  • 3
  • 23
  • 26
  • Please, take a look in to the UPD section of my question. – Johnny Doe Apr 06 '15 at 13:19
  • I believe the only reliable mechanism to do that is relying on the onWindowFocusChanged() method of the activity, thus exposing this event through the interface you have created. However, you still need to connect the interface with the activities themselves. – Jose L Ugia Apr 06 '15 at 13:30
0

Try this

    private Boolean isFragmentVisible()
    {
      if(getFragmentManager().findFragmentByTag("TAG") != null && getFragmentManager().findFragmentByTag("TAG").isVisible())
      {
        //The fragment is visible
        return true;
      }
      return false;
    }

Alternative way

    private Boolean isFragmentVisible()
    {
      return getFragmentManager().findFragmentByTag("TAG") != null && getFragmentManager().findFragmentByTag("TAG").isVisible();
    }
  • Unfortunately, i know nothing about tags or ids which were used during fragment transactions. I am looking something like "fragment lifecycle Observer". As far as i've looked deep into this problem - i found nothing. Anyway, it doesn't mean that solution isn't exist :) – Johnny Doe Apr 07 '15 at 05:46
0

You can know the following with the built in method called "onActivityCreated(Bundle)" this method tells that the fragment has been created thus you get to know that the fragment appears on the screen Click here for reference Hope it helps

silverFoxA
  • 4,549
  • 7
  • 33
  • 73
0

I've looked through what's available without using a base Fragment or Activity class but couldn't find any. I've made an implementation that provides basic (onAdded / onRemoved) functionality for all fragments in your application. It is certainly possible to extend it to report the current state of the fragment (onAttach, onResume, onPause, onDetach, ...).

You can find the code along with a sample here: https://github.com/Nillerr/FragmentLifecycleCallbacks

It works both for non-support library Fragments and support library Fragments through different implementations. The support library class is safer to use and should perform better, because the non-support one uses Reflection to access the fragments, while the support library FragmentManager includes a getFragments() method.

Nicklas Jensen
  • 1,424
  • 12
  • 19
0

If you are setting a Fragment to your View, you probably have a container where it will be shown. Given that this container is, say, a FrameLayout with id R.id.container, you can do that:

Fragment f = fragmentManager.findFragmentById(R.id.container);
if (f instanceof YourFragment) {
   // TODO something when YourFragment is ready
}
Teo Inke
  • 5,928
  • 4
  • 38
  • 37
0

Does this interface provide anything helpful to you?

https://github.com/soarcn/AndroidLifecyle/blob/master/lifecycle/src/main/java/com/cocosw/lifecycle/FragmentLifecycleCallbacks.java

It sounds like your best bet if you can't override the Fragment's own onResume() method is to create your own interface that extends ActivityLifecycleCallbacks, then put your logging code in the onFragmentResumed(Fragment yourFragment) method.

You can get a pointer to the Fragment by doing something like this:

int yourFragmentId = 0; //assign your fragment's ID to this variable; Fragment yourFragment.getId();
        FragmentManager fm = activity.getFragmentManager();
        Fragment f = fm.findFragmentById(yourFragmentId);
Chamatake-san
  • 551
  • 3
  • 10
0

whereever u want to check if fragment is visible or not.. just check isMenuVisible() value. this is fragment's method which i used to check visible fragment when i have to fire some http request from viewpager selected Item. hope this helps.

in my case i was using this method in onActivityCreated().

0

In you fragment override method setMenuVisibility If you are using ViewPager and are swiping from left and right, this method is called when the visivility of the fragment gets changed.

Here is a sample from my project

public abstract class DemosCommonFragment extends Fragment {

    protected boolean isVisible;

    public DemosCommonFragment() {
    }


    @Override
    public void setMenuVisibility(boolean menuVisible) {
        super.setMenuVisibility(menuVisible);
        isVisible = menuVisible;
        // !!! Do Something Here !!!
    }

}
Bojan Kseneman
  • 15,488
  • 2
  • 54
  • 59
0

Animation listener

I have NOT checked all use cases and there is an unhandled exception. You can play around with it to fit your use case. Please feel free to comment your opinions or use cases it did not solve.

NOTE: You can add fragmentWillDisappear and fragmentDidDisappear by handling for enter in onCreateAnimation.

Parent Fragment:

public class BaseFragment extends Fragment {
    
    private Animation.AnimationListener animationListener;
    
    private void setAnimationListener(Animation.AnimationListener animationListener) {
        this.animationListener = animationListener;
    }
    
    @Override
    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
        setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
                fragmentWillAppear(animation);
            }
    
            @Override
            public void onAnimationEnd(Animation animation) {
                fragmentDidAppear(animation);
            }
    
            @Override
            public void onAnimationRepeat(Animation animation) {
        
            }
        });
    }
    
    @Override
    public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
        
        AnimationSet animSet = new AnimationSet(true);
        Animation anim = null;
        try {
            anim = AnimationUtils.loadAnimation(getActivity(), nextAnim);
        } catch (Exception error) {
            
        }
        
        if (anim != null) {
            anim.setAnimationListener(animationListener);
            animSet.addAnimation(anim);
        }
        
        return animSet;
    }
    
    public void fragmentDidAppear(Animation animation) {
        
    }
    
    public void fragmentWillAppear(Animation animation) {
    
    }
    
}

Child Fragment:

class ChildFragment extends BaseFragment {
    
    @Override
    public void fragmentDidAppear(Animation animation) {
        super.fragmentDidAppear(animation);
    }
    
    @Override
    public void fragmentWillAppear(Animation animation) {
        super.fragmentWillAppear(animation);
    }
    
}
Matthew Mitchell
  • 514
  • 4
  • 14