A good way to do that is to define a callback interface inside the fragment and require that the host activity implement it. When the activity receives a callback through the interface, it can share the information with other fragments in the layout as necessary.
Edit:
The fragment has an onStart(...) which is invoked when the fragment is visible to the user. Similarly an onResume(...) when visible and actively running. These are tied to their activity counterparts.
In short: use onResume()
For a lack of a better solution, I got this working for me:
Assume I have 1 activity (MyActivity) and few fragments that replaces each other (only one is visible at a time).
(As you can see I'm using the compatibility package).
getListener implementation:
private OnBackStackChangedListener getListener()
{
OnBackStackChangedListener result = new OnBackStackChangedListener()
{
public void onBackStackChanged()
{
FragmentManager manager = getSupportFragmentManager();
if (manager != null)
{
MyFragment currFrag = (MyFragment) manager.findFragmentById(R.id.fragmentItem);
currFrag.onFragmentResume();
}
}
};
return result;
}
MyFragment.onFragmentResume() will be called after a "Back" is pressed. few caveats though:
It assumes you added all
transactions to the backstack (using
FragmentTransaction.addToBackStack())
It will be activated upon each stack
change (you can store other stuff in
the back stack such as animation) so
you might get multiple calls for the
same instance of fragment.
If a fragment is put on backstack, Android simply destroys its view. The fragment instance itself is not killed. A simple way to start should to to listen to the onViewCreated event, an put you "onResume()" logic there.
boolean fragmentAlreadyLoaded = false;
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onViewCreated(view, savedInstanceState);
if (savedInstanceState == null && !fragmentAlreadyLoaded) {
fragmentAlreadyLoaded = true;
// Code placed here will be executed once
}
//Code placed here will be executed even when the fragment comes from backstack
}
A little improved and wrapped into a manager solution.
Things to keep in mind. FragmentManager is not a singleton, it manages only Fragments within Activity, so in every activity it will be new. Also, this solution so far doesn't take ViewPager into account that calls setUserVisibleHint() method helping to control visiblity of Fragments.
Feel free to use following classes when dealing with this issue (uses Dagger2 injection). Call in Activity:
//inject FragmentBackstackStateManager instance to myFragmentBackstackStateManager
FragmentManager fragmentManager = getSupportFragmentManager();
myFragmentBackstackStateManager.apply(fragmentManager);
FragmentBackstackStateManager.java:
@Singleton
public class FragmentBackstackStateManager {
private FragmentManager fragmentManager;
@Inject
public FragmentBackstackStateManager() {
}
private BackstackCallback backstackCallbackImpl = new BackstackCallback() {
@Override
public void onFragmentPushed(Fragment parentFragment) {
parentFragment.onPause();
}
@Override
public void onFragmentPopped(Fragment parentFragment) {
parentFragment.onResume();
}
};
public FragmentBackstackChangeListenerImpl getListener() {
return new FragmentBackstackChangeListenerImpl(fragmentManager, backstackCallbackImpl);
}
public void apply(FragmentManager fragmentManager) {
this.fragmentManager = fragmentManager;
fragmentManager.addOnBackStackChangedListener(getListener());
}
}
FragmentBackstackChangeListenerImpl.java:
public class FragmentBackstackChangeListenerImpl implements FragmentManager.OnBackStackChangedListener {
private int lastBackStackEntryCount = 0;
private final FragmentManager fragmentManager;
private final BackstackCallback backstackChangeListener;
public FragmentBackstackChangeListenerImpl(FragmentManager fragmentManager, BackstackCallback backstackChangeListener) {
this.fragmentManager = fragmentManager;
this.backstackChangeListener = backstackChangeListener;
lastBackStackEntryCount = fragmentManager.getBackStackEntryCount();
}
private boolean wasPushed(int backStackEntryCount) {
return lastBackStackEntryCount < backStackEntryCount;
}
private boolean wasPopped(int backStackEntryCount) {
return lastBackStackEntryCount > backStackEntryCount;
}
private boolean haveFragments() {
List<Fragment> fragmentList = fragmentManager.getFragments();
return fragmentList != null && !fragmentList.isEmpty();
}
/**
* If we push a fragment to backstack then parent would be the one before => size - 2
* If we pop a fragment from backstack logically it should be the last fragment in the list, but in Android popping a fragment just makes list entry null keeping list size intact, thus it's also size - 2
*
* @return fragment that is parent to the one that is pushed to or popped from back stack
*/
private Fragment getParentFragment() {
List<Fragment> fragmentList = fragmentManager.getFragments();
return fragmentList.get(Math.max(0, fragmentList.size() - 2));
}
@Override
public void onBackStackChanged() {
int currentBackStackEntryCount = fragmentManager.getBackStackEntryCount();
if (haveFragments()) {
Fragment parentFragment = getParentFragment();
//will be null if was just popped and was last in the stack
if (parentFragment != null) {
if (wasPushed(currentBackStackEntryCount)) {
backstackChangeListener.onFragmentPushed(parentFragment);
} else if (wasPopped(currentBackStackEntryCount)) {
backstackChangeListener.onFragmentPopped(parentFragment);
}
}
}
lastBackStackEntryCount = currentBackStackEntryCount;
}
}
public abstract class RootFragment extends Fragment implements OnBackPressListener {
@Override
public boolean onBackPressed() {
return new BackPressImpl(this).onBackPressed();
}
public abstract void OnRefreshUI();
}
public class BackPressImpl implements OnBackPressListener {
private Fragment parentFragment;
public BackPressImpl(Fragment parentFragment) {
this.parentFragment = parentFragment;
}
@Override
public boolean onBackPressed() {
((RootFragment) parentFragment).OnRefreshUI();
}
}
and final extent your Frament from RootFragment to see effect
My workaround is to get the current title of the actionbar in the Fragment before setting it to the new title. This way, once the Fragment is popped, I can change back to that title.
@Override
public void onResume() {
super.onResume();
// Get/Backup current title
mTitle = ((ActionBarActivity) getActivity()).getSupportActionBar()
.getTitle();
// Set new title
((ActionBarActivity) getActivity()).getSupportActionBar()
.setTitle(R.string.this_fragment_title);
}
@Override
public void onDestroy() {
// Set title back
((ActionBarActivity) getActivity()).getSupportActionBar()
.setTitle(mTitle);
super.onDestroy();
}
pass FragmentTags.TAG_FOR_FRAGMENT_A.name() as fragment tag.
and now on
@Override
public void onBackPressed(){
FragmentManager fragmentManager = getFragmentManager();
Fragment current
= fragmentManager.findFragmentById(R.id.fragment_container);
FragmentTags fragmentTag = FragmentTags.valueOf(current.getTag());
switch(fragmentTag){
case TAG_FOR_FRAGMENT_A:
finish();
break;
case TAG_FOR_FRAGMENT_B:
fragmentManager.popBackStack();
break;
case default:
break;
}