I have a ViewPager that I am populating with fragments(representing objects from arrayListOfObjects) using FragmentStatePagerAdapter.
All works well:
mMyFragmentPagerAdapter = new fragmentAdapter(getSupportFragmentManager(),orientation
,rePopulatedfireInfoList);
mPager = (ViewPager)findViewById(R.id.fireInfoFragment_container);
initButton();
setTab();
mPager.setAdapter(mMyFragmentPagerAdapter);
The fragment adapter extends FragmentStatePagerAdapter.
From the primary activity I launch a dialog themed activity; where the user may add a new favourite location creating a new object which alters the arraylist of objects passed by the primary activity.
This is the code for starting dialog activity; all works fine:
Intent locationIntent = new Intent(afisController.this, locationActivity.class);
locationIntent.putExtra("firesList", new fireInfoListWrapper(arrayListOfObjects));
startActivityForResult(locationIntent,1);
The floating activity adds objects into arrayListOfObjects.
On the primary activity's onActivityResult I compare the arraListOfObjects I'm receiving with the one I sent; if different I want to completely remove the contents of the viewPager and recreate it with the new arrayListOfObjects. This is the onActivityResults:
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
Toast.makeText(this, "Activity Results fired..." , 1500 ).show();
if ((resultCode == 0) && (data != null)) {
Log.i("onActvityResult", "Inside resultCode check");
Bundle b = data.getExtras();
if(b != null){
Log.i("onActvityResult", "not null");
returnedFireInfoList = (ArrayList<fireInfo>) data.getSerializableExtra("firesListResult");
Log.i("onActvityResult", "results Size: "+returnedFireInfoList.size());
if(returnedFireInfoList.size()>0){
Log.i("onActvityResult", "locationName: "+returnedFireInfoList.get(0).getLocationName());
//compare returnedFireInfoList and rePopulatedfireInfoList, if different;
//add difference to rePopulatedfireInfoList and write back to file.
updateFireInfos(returnedFireInfoList, rePopulatedfireInfoList);
if(returnedFireInfoList.size()!=rePopulatedfireInfoList.size()){
mMyFragmentPagerAdapter1 = new fragmentAdapter(getSupportFragmentManager(),orientation
,returnedFireInfoList);
mPager = (ViewPager)findViewById(R.id.fireInfoFragment_container);
Log.i("updateFireInfos", "fragmentsCount is"+mPager.getCurrentItem());
fireInfoFragment fragment =
(fireInfoFragment) getSupportFragmentManager().findFragmentById(R.id.fireInfoFragment_container);
//This is where the problem is, I don't want to remember what was already on the viewPager //called mPager before.
// mPager.removeAllViews();
//mPager.setAdapter(null);
mMyFragmentPagerAdapter1.notifyDataSetChanged();
mPager.setAdapter(mMyFragmentPagerAdapter1); mMyFragmentPagerAdapter1.notifyDataSetChanged();
}
}
}
This is the fragmentStateAdapter code:
public class fragmentAdapter extends FragmentStatePagerAdapter {
private FragmentManager fragmentManager;
private FragmentTransaction mCurTransaction = null;
private ArrayList<Fragment.SavedState> mSavedState = new ArrayList<Fragment.SavedState>();
private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();
private Fragment mCurrentPrimaryItem = null;
public void restoreState(Parcelable state, ClassLoader loader) {
//Need to only delete info from marked fragments (theoned that are stored on orientationchange
//Currently redoing the entire call; resulting in delay due to server call
//if(isLastOrientationPortrait != isPortrait){
if(state != null){
Bundle bundle1 = (Bundle) state;
bundle1.setClassLoader(loader);
Iterable<String> keys = bundle1.keySet();
Log.i("restoreState", "containsKey FragmentStatePagerAdapter: "+keys);
//android.support.v4.app.FragmentManager fragmentManager= fragmentAdapter.this.fragmentManager;
android.support.v4.app.FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
//if (fragmentTransaction == null) {
// Log.i("restoreState", "fragmentTransactionTest");
fragmentTransaction = fragmentManager.beginTransaction();
// }
for (String key : keys) {
if (key.startsWith("f")) {
Fragment f = fragmentManager.getFragment(bundle1,
key);
fragmentTransaction.remove(f);
fragmentTransaction.commit();
}
}
}
//}
}
#Override
public int getItemPosition(Object object) {
// TODO Auto-generated method stub
//return super.getItemPosition(object);
return fragmentAdapter.POSITION_NONE;
}
public fragmentAdapter(android.support.v4.app.FragmentManager fragmentManager,String orientation,
ArrayList<fireInfo> fireInfoList) {
super(fragmentManager);
this.orientation = orientation;
this.fireInfoList = fireInfoList;
this.numItems = fireInfoList.size();
this.fragmentManager=fragmentManager;
}
ArrayList<fireInfo> fireInfoList;
String orientation;
int numItems;
#Override
public int getCount() {
Log.i("numItems", "is: "+fireInfoList.size());
return numItems;
}
#Override
public Fragment getItem(int arg0) {
Log.i("fragmentAdapterIndex", "is: "+arg0);
return fireInfoFragment.newInstance(orientation, fireInfoList.get(arg0));
}
}
Problem:
But the new ArrayListOfObjects is added alongside the old one before I fired the startActivityFor results.
How do I force the viewPager to forget it old content? basically reset the viewPager adapter with this newArrayListofObjects using my fragmentStateAdapter?
I guess the problem is in the fact that old fragments still reside in FragmentManager you use for your adapter. If this is the case all you have to do is remove all old fragments from the fragment manager.
So basically just execute the following code in the constructor of your adapter:
public fragmentAdapter(FragmentManager fragmentManager, String orientation, ArrayList<fireInfo> list) {
super(fragmentManager);
if (fragmentManager.getFragments() != null) {
fragmentManager.getFragments().clear();
}
//... your other code here
}
This line of code is unnecessary:
mMyFragmentPagerAdapter1.notifyDataSetChanged();
EDIT: It may be more correct to remove your fragments using FragmentTransaction:
List<Fragment> fragments = fragmentManager.getFragments();
if (fragments != null) {
FragmentTransaction ft = fragmentManager.beginTransaction();
for (Fragment f : fragments) {
//You can perform additional check to remove some (not all) fragments:
if (f instanceof AddedByCurrentPagerAdapterFragment) {
ft.remove(f);
}
}
ft.commitAllowingStateLoss();
}
This will take some time for FragmentTransaction to be (asynchronously) performed.
All I needed to do was to re-assign the FragmentStatePagerAdapter.
This has been in place as can be seen in onActivityResult but what was masking the correct behaviour was my viewPager page indicator it was incrementing the pages e.g. if I had 2 pages on the viewPager and call the child actvity which will add one object on the arrayListOfObjects; the viewPager page indicator would show that I now have 2 plus three pages (5).
I had to reset the viewPage indicator in onActivityResult to have it determined by this new arrayListOfObjects just returned by the floating activity.
Related
I have an Android app with a SlideMenu.
This is most of the MainActivity where I have the sideMenu:
public class MainActivity extends ActionBarActivity
{
private ActionBarDrawerToggle sideMenuToggle;
private DrawerLayout sideMenuLayout;
private SideMenuAdapter mAdapter;
...
private class DrawerItemClickListener implements ListView.OnItemClickListener
{
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
{
Bundle data = new Bundle();
data.putString("title", options.get(posit).getString("visual_name"));
if(posit == Constants.INDEX_USERNAME)
goToView(ViewManager.EDITPROFILE, data);
else if((posit >= Constants.INDEX_SECTION_1) && (posit <= Constants.INDEX_SECTION_5))
{
Bundle category = options.get(posit);
data.putString("title", category.getString("visual_name"));
data.putString("visual_name", category.getString("visual_name"));
data.putString("name", category.getString("name"));
data.putInt("photo", category.getInt("photo"));
data.putInt("ico", category.getInt("cat_ico"));
data.putBoolean("is_root", true);
goToView(ViewManager.CATEGORYFRAGMENT, data);
}
}
}
public Fragment goToView(ViewInfo _viewInfo, Bundle bundle)
{
try
{
if((this != null) && !this.isFinishing())
{
ViewManager myVMgr = viewMgr;
return myVMgr.show(_viewInfo, bundle, false);
}
} catch (IllegalStateException e) {
}
return null;
}
}
And this is most of the code of the ViewManager where I switch between sections (they are fragments):
public class ViewManager
{
private Activity context = null;
...
public Fragment show(ViewInfo _newFragInfo, Bundle _data, boolean back)
{
Fragment currentFragment = null;
Fragment newFragment = null;
final FragmentManager fm = context.getFragmentManager();
ViewInfo _lastFragInfo = lastViewData != null ? lastViewData.getViewInfo() : null;
// In this app we must support changing between same fragment class
//if((_lastFragInfo != null) && _newFragInfo.getIdView().equalsIgnoreCase(_lastFragInfo.getIdView())) {
// return null;
//}
FragmentTransaction ft = fm.beginTransaction();
if(_newFragInfo.getIsRoot())
{
Iterator<ViewData> iter = viewStack.iterator();
ViewData viewData;
while (iter.hasNext())
{
viewData = iter.next();
if(!viewData.getViewInfo().getIsRoot())
{
currentFragment = fm.findFragmentByTag(viewData.getViewInfo().getIdView());
if (currentFragment != null)
{
ft.remove(currentFragment);
}
}
iter.remove();
}
}
// Hide current fragment
if (_lastFragInfo != null)
{
currentFragment = fm.findFragmentByTag(_lastFragInfo.getIdView());
if (currentFragment != null)
{
if(!back)
ft.detach(currentFragment);
else
ft.remove(currentFragment);
}
}
// Show new fragment
if (_newFragInfo != null)
{
if(_newFragInfo.getIsRoot() || back) // only tabs are reusable fragment
newFragment = fm.findFragmentByTag(_newFragInfo.getIdView());
if (newFragment == null)
{
newFragment = Fragment.instantiate(context, _newFragInfo.getClaseView().getName());
ft.add(R.id.frame_content, newFragment, _newFragInfo.getIdView());
}
else
{
ft.attach(newFragment);
}
if(_data == null)
_data = new Bundle(1);
if(!_data.containsKey("title"))
_data.putString("title", context.getResources().getString(_newFragInfo.getTitle()));
if(!_newFragInfo.getIsRoot() && !back)
{
viewStack.add(lastViewData);
}
((BaseFragment)newFragment).setData(_data);
}
lastViewData = new ViewData(_newFragInfo, _data);
ft.setCustomAnimations(android.R.animator.fade_in, android.R.animator.fade_out);
ft.commitAllowingStateLoss();
fm.executePendingTransactions();
return newFragment;
}
}
In the sideMenu, "Edit profile" option has its own Fragment and Section 1 to Section 5 use the same fragment but I load a different content.
This is working well so far and when the user opens the sidemenu and changes between sections they are loaded correctly.
But now I've uploaded my code to use target sdk 26 and it doesn't compile because ActionBarActivity is no longer available so I've made these changes:
public class MainActivity extends ActionBarActivity
to this
public class MainActivity extends AppCompatActivity
and inside ViewManager
private Activity context = null;
to this
private AppCompatActivity context = null;
This now compiles but when I change between sections the data of the new section is no loaded and I keep watching the content of the previous section.
Some examples:
1)
1.1) I select Section1 (I see the content of Section1)
1.2) I select Edit Profile (I see the content of Edit profile)
1.3) I select Section3 (I see the content of Section3)
2)
2.1) I select Section1 (I see the content of Section1)
2.2) I select Section3 (I don't see the content of Section3 but the content of Section1).
Checking this I've seen that the problem is because with AppCompatActivity, onCreate, onCreateView and onResume methods of my CategoryFragment are called in steps 1.1, 1.3 and 2.1 but not in step 2.2.
Am I missing something? Should I make a new change so the Fragments transactions are made correctly with AppCompatActivity?
-- EDIT --
The setData method of the Fragment is this:
public void setData(Bundle _newdata)
{
data = _newdata;
}
I've thought to change it to this:
public void setData(Bundle _newdata)
{
data = _newdata;
// Some code to force the refresh/reloading of the Fragment
}
But I'm not sure if this is a tricky way to fix this, and by the way, I don't know how to force a fragment to reload itself.
I have two fragments that are in the main activity and i want to refresh them when something occurs.
Now the code works for second fragment, but won't work for the first, and i am not sure why.
I have been looking at the code for about an hour, and i can't seem to find a reason.
Here is the code
public class MainActivity extends FragmentActivity {
Fragment frag,frag2;
FragmentManager fm;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String screen = getString(R.string.screen_type);
/*
* Get a reference to fragment manager
* Wire the container to represent fragment
*/
fm = getSupportFragmentManager();
frag = fm.findFragmentById(R.id.container);
if(screen.equals("large")){
frag2 = fm.findFragmentById(R.id.containerDetails);
loadFragments(frag,frag2,fm);
}
/*Loads the fragment into the activity*/
else
loadFragment(frag,fm);
}
private void loadFragments(Fragment frag, Fragment frag2, FragmentManager fm) {
if(frag == null && frag2 == null){
frag = new DisplayFragment();
frag2 = new DetailsFragment();
fm.beginTransaction().add(R.id.container,frag).add(R.id.containerDetails, frag2).commit();
}
}
private void loadFragment(Fragment frag, FragmentManager fm) {
if(frag == null){
frag = new DisplayFragment();
fm.beginTransaction().add(R.id.container,frag).commit();
}
}
public void updateDetails(int position) {
// Reload current fragment
if(frag2!=null)fm.beginTransaction().remove(frag2).commit();
frag2 = new DetailsFragment();
Bundle b = new Bundle();
b.putInt("Id",position);
frag2.setArguments(b);
fm.beginTransaction().add(R.id.containerDetails, frag2).commit();
}
public void updateDisplay() {
// Reload current fragment
if(frag!=null)fm.beginTransaction().remove(frag).commit(); //THIS IS ALWAYS NULL FOR SOME REASON
frag = new DisplayFragment();
fm.beginTransaction().add(R.id.container, frag).commit();
}
public void refreshDetails() {
// Reload current fragment
if(frag2!=null)fm.beginTransaction().remove(frag2).commit();
frag2 = new DetailsFragment();
fm.beginTransaction().add(R.id.containerDetails, frag2).commit();
}
}
The first fragment is always null, and it doesn't get removed, instead another fragment is pasted over that, and creates a mess.
Try using replace() method rather than add()
I have a few fragments in my app, but my code opens a new fragment every time I click the button.
I want to know how can I change this, and make the fragment return to the exact same state I left it in.
The code im using right now:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fragments);
MainActivity fragment = new MainActivity();
FragmentTransaction transaction = getSupportFragmentManager()
.beginTransaction();
transaction.add(R.id.fragment_place, fragment);
transaction.commit();
turnGPSOn();
}
public void onSelectFragment(View view) {
if (view == findViewById(R.id.add))
{
newFragment = new Add();
}
else if (view == findViewById(R.id.map))
{
newFragment = new MainActivity();
}
else
{
newFragment = new Add();
}
FragmentTransaction transaction = getSupportFragmentManager()
.beginTransaction();
transaction.replace(R.id.fragment_place, newFragment);
transaction.addToBackStack(null);
transaction.commit();
}
Thanks!
You are getting a new fragment each time because you are calling to new XXX() each time.
I think you could use findFragmentByTag in order to solve this problem. As you can see here the replace function can accept a third parameter that is a String, this String can be used as an id to identify different fragments you have used previously.
So to sum up you can:
Call Fragment f = getSupportFragmentManager().findFragmentByTag("FragAdd"); for example in order to retrieve the first fragment.
If f is null, that means that you haven't used that fragment yet, so you have to call to new Add() if not, use that fragment to replace the old one. For example like this:
FragmentTransaction transaction = getSupportFragmentManager()
.beginTransaction();
transaction.replace(R.id.fragment_place, newFragment, "FragAdd"); //or whatever other string you want to use
transaction.addToBackStack(null);
transaction.commit();
Hope it helps :)
I faced this issue a time ago, and managed to solve it for applications with one visible fragment at a time; for activities with several visible fragments, you'll need to make some adjustments. This is what I did.-
Create a custom ParentActivity, so that all my activities extend it. ParentActivity knows about which is the current Fragment that is showed, and how to show a new one.
public String currentFragmentTag;
public ParentFragment getCurrentFragment(int fragmentWrapperResId) {
ParentFragment res = null;
FragmentManager fragmentManager = getSupportFragmentManager();
res = (ParentFragment) fragmentManager.findFragmentById(fragmentWrapperResId);
if (res != null && res.isHidden()) {
if (currentFragmentTag != null) {
res = (ParentFragment) fragmentManager.findFragmentByTag(currentFragmentTag);
}
}
return res;
}
public void openFragment(ParentFragment fragment, int fragmentWrapperResId, int enterAnim, int exitAnim, int popEnterAnim, int popExitAnim, boolean addToBackStack) {
FragmentManager fragmentManager = getSupportFragmentManager();
ParentFragment currentFragment = getCurrentFragment(fragmentWrapperResId);
if (currentFragment != null && currentFragment.getTagName().equals(fragment.getTagName())) {
return;
}
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim);
if (currentFragment != null) {
transaction.hide(currentFragment);
}
if (fragment.isAdded()) {
transaction.show(fragment);
} else {
transaction.add(fragmentWrapperResId, fragment, fragment.getTagName()).setBreadCrumbShortTitle(fragment.getTagName());
}
if (addToBackStack) {
transaction.addToBackStack(fragment.getTagName());
} else {
currentFragmentTag = fragment.getTagName();
}
transaction.commit();
}
Create a ParentFragment, to be extended by the rest of Fragments, with a tag getter
public String getTagName() {
return getClass().getSimpleName() + System.identityHashCode(this);
}
As you can see, the main idea is not replacing visible fragments, but just adding them and show/hide whenever it's needed. This way, the fragments will keep their states, as they're not destroyed until you remove them from the bakstack.
I am currently trying to make framework for my future apps. I really like the ActionBar and the ViewPager, but miss a feature. I need to replace a Fragment/Tab in runtime.
Using the offical example, I would love to see something like a replaceTab(), but the Fragment itself isn't updated, no matter what I do.
You have to use a FragmentPagerAdapter to handle your fragment changes. There are 3 important things:
remove the previous fragment if it exists.
call notifyDataSetChanged() to refresh the list of pages.
return POSITION_NONE in getItemPosition if it's asking for an old fragment.
Here is the code I use with a Left and a Right page. I change dynamically the fragments depending on what the user does.
public class MyPager extends ViewPager {
private MyPagerAdapter mMyPagerAdapter;
public MyPager(Context context, FragmentActivity activity) {
super(context);
mMyPagerAdapter = new MyPagerAdapter(activity.getSupportFragmentManager());
setAdapter(mMyPagerAdapter);
}
public void replaceLeftFragment(Fragment fragment) {
mMyPagerAdapter.replaceLeftFragment(fragment);
}
public void replaceRightFragment(Fragment fragment) {
mMyPagerAdapter.replaceRightFragment(fragment);
}
}
public class MyPagerAdapter extends FragmentPagerAdapter {
private FragmentManager mFragmentManager;
private Fragment mLeftFragment;
private Fragment mRightFragment;
public MyPagerAdapter(FragmentManager fm) {
super(fm);
mFragmentManager = fm;
}
#Override
public Fragment getItem(int position) {
if (position == 0) {
return mLeftFragment;
} else {
return mRightFragment;
}
}
#Override
public int getCount() {
final int nbLeft = (mLeftFragment == null) ? 0 : 1;
final int nbRight = (mRightFragment == null) ? 0 : 1;
return (nbLeft + nbRight);
}
public void replaceLeftFragment(Fragment fragment) {
if (mLeftFragment != null) {
mFragmentManager.beginTransaction().remove(mLeftFragment).commit();
}
mLeftFragment = fragment;
notifyDataSetChanged();
}
public void replaceRightFragment(Fragment fragment) {
if (mRightFragment != null) {
mFragmentManager.beginTransaction().remove(mRightFragment).commit();
}
mRightFragment = fragment;
notifyDataSetChanged();
}
#Override
public int getItemPosition(Object object) {
if ((object!=mLeftFragment) && (object!=mRightFragment)) {
return POSITION_NONE;
}
return super.getItemPosition(object);
}
}
Do you try:
fragmentTransaction.remove(yourframent);
and then:
fragmentTransaction.add(yournewfragment);
If your Fragment is created from a layout file, you cannot replace it at runtime. You can only successfully replace Fragments you added programmatically. See Android: can't replace one fragment with another
Very close to what you want, I needed to add fragment (at the end) to a ViewPager, and did this:
FragmentTransaction ft = fragmentManager.beginTransaction();
new_fragment = Fragment.instantiate(activity, class_name, args);
// The below code removes old fragment and adds the new one at the end.
ft.attach(new_fragment); // <- this adds the fragment at the end
ft.detach(old_fragment); // <- this removes old fragment
// This should be what you're looking for when adding to an Activity instead of a ViewPager
ft.replace(view_id, new_fragment); // <- this is supposed to replace the fragment attached to view_id
// Or if the replace does not work, this might work:
ft.detach(old_fragment);
ft.add(view_id, new_fragment);
// Not to be forgotten: ;)
ft.commit();
The above code might need some adjustments, maybe only require the replace call to effectively work?
If the replace call fails, you can still detach all fragments and re-attach them in the order desired.
I'm using the compatibility package to use Fragments with Android 2.2.
When using fragments, and adding transitions between them to the backstack, I'd like to achieve the same behavior of onResume of an activity, i.e., whenever a fragment is brought to "foreground" (visible to the user) after poping out of the backstack, I'd like some kind of callback to be activated within the fragment (to perform certain changes on a shared UI resource, for instance).
I saw that there is no built in callback within the fragment framework. is there s a good practice in order to achieve this?
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).
In MyActivity, add this listener:
getSupportFragmentManager().addOnBackStackChangedListener(getListener());
(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.
I've changed the suggested solution a little bit. Works better for me like that:
private OnBackStackChangedListener getListener() {
OnBackStackChangedListener result = new OnBackStackChangedListener() {
public void onBackStackChanged() {
FragmentManager manager = getSupportFragmentManager();
if (manager != null) {
int backStackEntryCount = manager.getBackStackEntryCount();
if (backStackEntryCount == 0) {
finish();
}
Fragment fragment = manager.getFragments()
.get(backStackEntryCount - 1);
fragment.onResume();
}
}
};
return result;
}
After a popStackBack() you can use the following callback : onHiddenChanged(boolean hidden) within your fragment
The following section at Android Developers describes a communication mechanism Creating event callbacks to the activity. To quote a line from it:
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()
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
}
In my activity onCreate()
getSupportFragmentManager().addOnBackStackChangedListener(getListener());
Use this method to catch specific Fragment and call onResume()
private FragmentManager.OnBackStackChangedListener getListener()
{
FragmentManager.OnBackStackChangedListener result = new FragmentManager.OnBackStackChangedListener()
{
public void onBackStackChanged()
{
Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if (currentFragment instanceof YOURFRAGMENT) {
currentFragment.onResume();
}
}
};
return result;
}
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;
}
}
BackstackCallback.java:
public interface BackstackCallback {
void onFragmentPushed(Fragment parentFragment);
void onFragmentPopped(Fragment parentFragment);
}
This is the correct answer you can call onResume() providing the fragment is attached to the activity. Alternatively you can use onAttach and onDetach
onResume() for the fragment works fine...
public class listBook extends Fragment {
private String listbook_last_subtitle;
...
#Override
public void onCreate(Bundle savedInstanceState) {
String thisFragSubtitle = (String) getActivity().getActionBar().getSubtitle();
listbook_last_subtitle = thisFragSubtitle;
}
...
#Override
public void onResume(){
super.onResume();
getActivity().getActionBar().setSubtitle(listbook_last_subtitle);
}
...
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();
}
I have used enum FragmentTags to define all my fragment classes.
TAG_FOR_FRAGMENT_A(A.class),
TAG_FOR_FRAGMENT_B(B.class),
TAG_FOR_FRAGMENT_C(C.class)
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;
}