I have an application with a navigation drawer, one activity and many fragments. When I change the device orientation my main activity is recreate and it's my main fragment who appears, not the current fragment. Just like when I launch the app.
How can I restore the current fragment and maybe the data associated with the selected fragment ?
I Have seen this post but it doesn't work : Save fragment state with navigation drawer
Thanks in advance to those who can help me.
I've found that the fragment states are already persisted for you.
This is what I do: I check if savedInstanceState is null. If it is, I execute the FragmentManager transaction to display the main fragment. If it is not null, it means the fragment state is persisted. I don't do any kind of transaction, and the fragment is recreated for me from the persisted state.
if (savedInstanceState == null){
//Launch HomeFragment onStart...
Fragment fragment = new HomeFragment();
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.frame, fragment).commit();
}
I added this to onCreate in my Main Activity
Related
I'm having problem with fragment. Lets try to understand my issue, I have two fragment A and B. When app start with main activity,i start fragment A as you can see :
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container,new MusicFragment())
.commit();
When i click on a button, it starts fragment B
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container,new BarFragment())
.addToBackStack(null)
.commit();
Main problem is after starting fragment B,when i pressed back to go back to fragment A , Fragment A Recreated with new state.
I don' want to recreate fragment A. I only want to start fragment from old state where i left. How to fix it ?
Instead of calling the replace method you should be calling the add method with a subsequent call to addToBackStack with an argument of null. The add method adds the fragement to the stack with a null tag and calling the addToBackStack with an argument of null then the current fragment is stopped upon commit. If the method is not called then the current fragment is destroyed and recreated when coming back.
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container,new BarFragment())
.addToBackStack(null)
.commit();
You can clearly find it in the documentation quote saying this:
If you don't call addToBackStack() when you perform a transaction that
removes a fragment, then that fragment is destroyed when the
transaction is committed and the user cannot navigate back to it.
Whereas, if you do call addToBackStack() when removing a fragment,
then the fragment is stopped and is later resumed if the user
navigates back.
Here are some points that need to be taken care while creating fragments.
Check the backstack if the fragment is already created.
If it was created previously pop it from the backstack and put it on top so that it is visible to the user.
If fragment is not present in backstack crate it and store it in backstack.
Create a method like below which will handle such situation.
private void openFragment(Fragment fragment_to_be_opened){
String fragment_to_be_opened_name = fragment_to_be_opened.getClass().getName();
FragmentManager manager = getSupportFragmentManager();
// fetching the fragment if it is present in backstack
boolean fragment_allready_present = manager.popBackStackImmediate (fragment_to_be_opened_name, 0);
if (!fragment_allready_present){ //fragment is not present in backstack so create it and save the name in //backstack
FragmentTransaction fragment_trasition = manager.beginTransaction();
fragment_trasition.replace(R.id.fragment_container, fragment_to_be_opened);
fragment_trasition.addToBackStack(fragment_to_be_opened_name);
fragment_trasition.commit();
}
}
Now call this method from the activity to open a new fragment like
// create instance of fragment and pass it to the open fragment method
Fragment myFragment = new myFragment();
openFragment(myFragment);
I have an activity that has a container. And i have two fragments that I'm replacing into this container. The fragments are fragmentList and fragmentDetail
First time I'm adding the list fragment into the container
MainActivity OnCreate
if (savedInstanceState == null) {
ListFragment listFragment = new ListFragment();
ft.add(R.id.fragment_container, listFragment, "LIST");
ft.commit();
}
When the user clicks on the list, I replace the container with the detail fragment
ActionButton
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, detailFragment);
fragmentTransaction.commit();
http://www.vogella.com/tutorials/Android/images/xfragmentsusage10.png.pagespeed.ic.MkyF4ZO5Hr.png
The problem came when I restart the activity. How I can know if I need to request the list data again, or if the detail fragment are shown? Because in my onResume method both fragments are null.
you are already checking the savedInstanceState which is your first step
check this document for details:
http://developer.android.com/intl/es/training/basics/activity-lifecycle/recreating.html
the key is very simple, just save your current state in the onSaveInstanceState method, and restore the state on onRestoreInstanceState
the savedInstanceState is a bundle , Bundle objects are used to store and pass data between activities and fragments and so, you need to store a boolean variable in it to determine which fragment are you showing, and restore this fragment on resume according to this boolean flag
You can use
getFragmentManager().FindFragmentByTag(string tag)
And check if it is null and then you can check isVisible on the fragment returned, generally you'll need to reset your adapters as well
Hardly can't get fragment from backstack, even start thinking of keeping in in singleton which is probably bad.
Saved to backstack like this and all time try to get it by tag or something gives me error.
Fragment fragment = UserProfileFragment.newInstance(null);
FragmentTransaction trans = getFragmentManager().beginTransaction();
trans.replace(FRAGMENT_PLACE_RESOURCES, fragment);
trans.addToBackStack("profile");
trans.commit();
It just return me null here so I can't use this fragment. No logs.
Fragment fragment2 = getFragmentManager().findFragmentByTag("profile");
getFragmentManager().findFragmentByTag("tag")
is only used when you have added a fragment with specific tag for e.g.
fragmentTransaction.add(R.id.order_container,mProfileFragment,"profile");
or
fragmentTransaction.replace(R.id.order_container,mProfileFragment,"sometag");
Then you will be able to find this fragment by the tag.
In your case you are adding a transaction to backstack so you will not be able to find that fragment by tag. You just adding a transaction to backstack thats it not a fragment. And also your fragment was removed from the activity and destroyed so you have to revert the transaction by popping backstack instead of finding that fragment by tag.
You have to call
getFragmentManager().popBackStack("profile");
to get that fragment back to the activity and make it visible on screen.
I write this question because I would like to know which is the best way to manage this context: I have a MainActivity with Navigation Drawer and whenever I select an item in Navigation Drawer, I create a new fragment and through the FragmentTransaction I replace the previous fragment with the new one.
Now, in every fragment I have an AsyncTask that performs some task (eg download data from web or perform a query on a local sqlite database).
My question is: how can I avoid to recreate every time the fragment and restart AsyncTask when I press an element in Navigation Drawer? Which is the best way to manage this situation?
This is the method that I use in MainActivity to display fragment when I press an item in Navigation Drawer:
private void displayView(int index) {
Fragment f = null;
switch(index) {
case 1:
f = Fragment1.newInstance();
break;
case 2:
f = Fragment2.newInstance();
break;
}
if(f != null) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.container, f);
ft.commit();
}
}
Thanks in advance!
Hiding a fragment is an alternative to removing it. Removal (if not added to the backstack) causes the fragment to be completely torn down (pause, stop, destroyView, destroy, detach). Hiding doesn't change the fragment's lifecycle state, it just makes it not visible. The FragmentTransaction methods are hide() and show(). Initially, you would add both your fragments, and then hide the unselected one. When a fragment is selected from the navigation drawer, if it is not already visible, you would hide the old selection and make the new selection visible. Something like this:
getSupportFragmentManager()
.beginTransaction()
.hide(oldSelectedFrag)
.show(newSelectedFrag)
.commit();
The visible/hidden status of a fragment is available using isHidden(). There is also a Fragment callback: onHiddenChanged(). Keep in mind that when a fragment is hidden it is still active in the sense of being in the started or resumed state. You may want to use the hidden status to disable refreshes or other actions that are only needed when it is visible. If the fragment has an option menu, you may also want disable that using setMenuVisibility() when the fragment is hidden. I don't think the FragmentManager does that for you automatically.
I have two fragments - Listing and detail. Initially I load the listing fragment in the container of the activity using
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, listingFrag);
ft.commit();
On tapping an item in the listing fragment I load the detail fragment using
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.add(R.id.content_frame, detailFrag);
ft.addToBackStack(null);
ft.commit();
On loading detail fragment I do not get the onPause call of the listing fragment. Neither do I get the onResume call of the listing fragment on coming back from detail fragment (using the system back button).
Also, when I am in the detail fragment, putting the app to background calls the onPause of both the listing and detail fragments. On getting back the app from background, onResume of both listing and detail screens get called.
The above mentioned behaviors are quite unexpected.
I would want
1) listing fragment's onResume to be called on coming back from listing screen
2) listing fragment's onPause to be called on loading detail fragment
3) only detail fragment's onPause to be called when the app is put to background
4) only detail fragment's onResume to be called when the app is brought back from background
Can some one please explain a way to do this.
Thanks in advance!
onPause and onResume in fragment are just the reflection of onPause and onResume in Activity.
So every time you go from one activity to another all the fragments of the first activity will call onPause and all the fragments of the 2nd activity will call onResume.
This functions are not for fragment visible/add or remove.
When you display the detailed fragment, you add it which means that both the listing and the detailed fragments are active at the same time. Hence both of them receive onPause() and onResume().
You should instead try replacing the listing fragment with the details fragment. That will cause onPause() in the listing fragment and only one fragment will be active at a time.
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, listingFrag);
ft.addToBackStack(null);
ft.commit();
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, detailFrag);
ft.addToBackStack(null);
ft.commit();