i have a piece of code where i wish to just hide the current fragment so it doesnt destroy its view and then show a new one using this :
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.hide(oldFragment);
ft.show(newFragment);
ft.commit();
The issue is that when i execute the above code, it doesnt show any UI components.
if i do ft.replace(id,fragment); it works but i do not want to remove the previous displayed fragment as i want to maintain the fragments and its views so i dont need to re-initialise it
Did you previously add newFragment to some part of your Activity's view hierarchy? If you just instantiate a Fragment and tell it to show, it won't know where to show (unless it's a DialogFragment, I guess). You need to use add(somelayoutid,fragment,"sometag") for each Fragment and then you can hide/show them as you'd like. You can also just continually use replace, rather than hide/show, if you don't need to keep your Fragment's around while they're hidden.
"sometag" will be useful if you're handling rotation so you can retrieve a reference to each Fragment after your Activity is recreated, and then you can hide/show them as before.
Was your fragment initialized before calling this?
if (newFragment == null) {
// If not, instantiate and add it to the activity
ft.add(yourFragmentContainerId, newFragment,"tag");
} else {
// If it exists, simply attach it in order to show it
ft.show(newFragment);
}
Related
Is there a way to know which Fragment is currently displayed in a given <fragment> container of an Activity without keeping track of all the changes via the onAttachFragment callback?
Is it even possible to know which fragments are displayed when fragment transactions can take place when the user presses the back key? In this latter case, i.e. when a Fragment is re-displayed due to a back, the onAttach is not called.
In my experience, the only way to know for sure which fragment is being displayed is to keep track of that carefully yourself.
For example, you could make a variable in your Activity:
Fragment mCurrentDisplayedFragment;
and then whenever the user requests a different fragment do:
mCurrentFragment = (Fragment) userRequestedFragment;
fragmentManager.replace(container, mCurrentFragment, tag);
Then, whenever you needed to do things to the currently displayed fragment, you could triage it by try/catching a cast or with instanceof.
You could also handle the back pressed behavior by overriding that method in the activity:
#Override
public void onBackPressed() {
int stackSize = fragmentManager.getBackStackEntryCount();
// This counts up from the bottom so the most recent fragment is the biggest number/size
backFragId = fragmentManager.getBackStackEntryAt(stackSize);
// Get a handle on the fragment that is about to be popped
mCurrentFragment = fragmentManager.findFragmentById(backFragId);
super.onBackPressed();
}
Also, are you sure that onAttach is not called when a fragment is popped off the stack? I seem to remember that it will be, and you can call through the interface created there (if you have one and the activity implements it) to register the fragment as the current fragment in the activity at the time.
But to directly answer your question, there isn't a built in way to just know what fragment is currently displayed (and there could be more than one!). The implementation details of that are up to you. Hopefully I've given you some ideas of how it could be handled though. You might also find the FragmentManager documentation helpful.
Each time when you add/replace fragment to the container, use tag for it:
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.add(R.id.container, fragment, tag).commit();
then you can find out the fragment is current visible or not:
Fragment fg = getFragmentManger().findFragmentByTag(tag);
if(fg.isVisible())
//fg is the current visible fragment
Hope this help!
I am trying to implement a method where I want to return to the previous fragment and destroy the current one. However, when I add the fragment to the Backstack it doesn't get destroyed anymore afterwards.
Is there any way to destroy it? Or maybe to return to the previous fragment without using the Backstack?
Edit:
I want to use the backwards navigation as well.
Firstly if you are using the BackStack, it is not typical to need to specifically manually remove Fragments, which suggests you might want to have another think about your design.
That said, to specifically manually remove a Fragment, Override onBackPressed in your Activity which is showing the Fragments, manually remove the Fragment there.
To make it easy to determine which Fragment is currently showing, you can give it a Tag when you show it. For example:
fragTrans.replace(android.R.id.content, myFragment, "MY_FRAGMENT_X");
Then in the onBackPressed function of your Activity
#Override
public void onBackPressed()
{
FragmentManager fragMan = getFragmentManager();
// Check if that Fragment is currently visible
MyFragment myFragment = (MyFragment)fragMan.findFragmentByTag("MY_FRAGMENT_X");
boolean myFragXwasVisible = myFragment.isVisible();
// Let the Activity pop the BackStack as normal
super.onBackPressed();
// If it was your particular Fragment that was visible...
if (myFragXwasVisible)
{
FragmentTransaction trans = fragMan.beginTransaction();
trans.remove(myFragment).commit();
}
}
Note: When it comes to specifically destroying your Fragment object, that is what Java's garbage collection is for. You don't need to worry about that yourself, Java will take care of destroying it when it needs to. That's the whole point.
I have the following setup which is quite common: in landscape mode I have 2 fragments - A and B. In portrait mode I have only fragment A. I tried to detect if I am in second setup mode by just a simple check:
getSupportFragmentManager().findFragmentById(R.id.frag_b) == null
This was working fine until I was in 2 fragment mode and rotating the device to 1 fragment mode - after that the manager was finding the fragment B and not returning null. I believe the fragment manager was somehow saving and loading its state from previous setup. The first question - why is this working this way and what can I do with it?
Second question - I tried to remove the fragment but was not able to do that. Here how I tried:
Fragment f = manager.findFragmentById(R.id.frag_b);
manager.beginTransaction().remove(f).commit();
f = manager.findFragmentById(R.id.frag_b); // still there
I guess remove() didn't work since it was not added using add() but rather loaded from previous state xml. Is there a way to remove a fragment from manager in this case?
P.S. The solution for me will be to have another way of detection in which mode I am. I already have this, just need to know how it works for better understanding of fragments and their behavior.
you can detect if you are on portrait or landscape with this:
getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
Being new to fragments and after struggling a day I think I understood most of the logic behind fragments and their usage. The fact that fragment manager shows fragments other than the ones defined for current orientation is not a bug, it's a feature. Here are some observations summarized:
When changing configuration, the FragmentManager saves all the fragments it has currently and loads them back so they are ready in onCreate() method of container activity. This means that if you have fragment A and B in some layout and you rotate the device to a state where only A should be - you still will find B in FragmentManager. It might be not added (check with Fragment.isAdded()) or might be added to some other container, which is not visible now, but its there. This is quite useful since B saves its state (given that you did it properly in B's life cycle functions) and you don't have to take care of it on activity level. Maybe, at some point in future, you would like to dynamically add fragment B to your UI and it will have all its state saved from previous configuration.
Related to the second question above - fragments that need to be moved from container to container should not be declared in xml, they should be added dynamically. Otherwise you will not be able to change its container and will get IllegalStateException: Can't change container ID of fragment exception. Instead, you define containers in XML file and give them an ID, for example:
<RelativeLayout
android:id="#+id/fragmentContainer"
android:layout_height="match_parent"
android:layout_width="0dp"
android:layout_weight="0.7" />
and later add to it using something like
FragmentManager.beginTransaction().add(R.id.fragmentContainer, fragment);
If you need to use some fragment, first look if FragmentManager has it - if yes, then just reuse it - it will have its state saved as a bonus. To look for fragment:
private void MyFragment getMyFragment() {
List<Fragment> fragments = getSupportFragmentManager().getFragments();
if (fragments != null) {
for (Fragment f : fragments) {
// an example of search criteria
if (f instanceof MyFragment) {
return (MyFragment) f;
}
}
}
return null;
}
if it is null then create a new one, otherwise you can go ahead and reuse it, if needed.
One way of reusing a fragment is putting it in another container. You will need some effort in order not to get IllegalStateException: Can't change container ID of fragment. Here is the code I used with some comments to help understand it:
private void moveFragment(MyFragment frag) {
int targetContainer = R.id.myContainerLandscape;
// first check if it is added to a correct place
if (frag.isAdded()) {
View v = frag.getView();
if (v != null) {
int id = ((ViewGroup) v.getParent()).getId();
if (id == targetContainer) {
// already added to correct container, skip
return;
}
}
}
FragmentManager manager = getSupportFragmentManager();
// Remove the fragment from its previous container first (done
// here without check if added or nor, check if needed).
// In order not to get 'Can't change container ID...' exception
// we need to assure several things:
// 1. its not hardcoded in xml - you can remove()
// fragment only if you have add()-ed before
// 2. if this fragment is sitting deep in a backstack
// then you will still get the above mentioned exception.
// If the stack is fragA-fragB-fragC <-top then you get
// exception on moving fragment A. Need to clean the back
// stack first. HOWEVER, note that in that case you will
// lose the fragB and fragC together with their states!
// For that reason save them first - I will assume there is
// only one on top of current fragment to make code simpler.
// before cleaning save the top fragment so that it is not destroyed
OtherFragment temp = getOtherFragment(); // use function 'getMyFragment()' above
// clean the backstack
manager.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
// remove the fragment from its current container
manager.beginTransaction().remove(frag).commit();
// call executePendingTransactions() for your changes to be available
// right after this call, otherwise the previous 'commit()' just submits
// the task to main thread and it will be done somewhere in the future
manager.executePendingTransactions();
if (getOtherFragment() == null && temp != null) {
// Add the fragment we wanted to 'save' back to manager, this
// time without any relation to backstack or container. Later
// we will be able to find it using getOtherFragment() and the
// fragment manager will be able to save/load its state for us.
manager.beginTransaction().add(temp, null).commit();
manager.executePendingTransactions();
}
// now add our fragment
FragmentTransaction transaction = manager.beginTransaction();
transaction.replace(targetContainer, frag);
transaction.commit();
manager.executePendingTransactions();
}
This was the result of my first day of dealing with fragments seriously, at least my task was accomplished in my app. Would be good to get some comments from experienced guys on what is wrong here and what can be improved.
I have an activity that have some buttons and some fragments.
If I click on button A, i'll show Fragment "FragA". When I'm in "FragA", I can perform some actions like choose a picture from gallery and I need to stay in "FragA" after choose picture.
But when I choose picture, I return to Activity and "FragA" is hidden.
How can perform an action and still in same Fragment or display correct fragment in Activity?
You can recreate fragment again and replace it in your Activity with using modification of this code:
if (currentState == STATE_MAIN_FRAGMENT) {
return;
}
mainScreenFragment = (MainScreenFragment) getSupportFragmentManager().findFragmentByTag(MainScreenFragment.TAG);
if (mainScreenFragment == null) {
mainScreenFragment = new MainScreenFragment();
}
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.flFragmentContainer, mainScreenFragment, MainScreenFragment.TAG);
fragmentTransaction.commit();
First "if" checks if the fragment is set or not. It is not required but it's a good practice. It prevents you from replacing fragment when it is not necessary.
And there is one thing strange for me. Because you said <<"FragA" is hidden>> - that means it was already set but container is not visible? Then yourFragmentContainer.setVisiblity(View.VISIBLE); in on Activity result.
And the last thing that could help you is to retain the fragment so it won't be ever destroyed and recreated again. Some helpful links:
Understanding Fragment's setRetainInstance(boolean)
http://developer.android.com/reference/android/app/Fragment.html#setRetainInstance(boolean)
Or you can just copy-paste what is in your Button's OnClickListener so it happens onActivityResult too.
I am using Fragments to represents different views in my application. I replace the fragments using the following code when navigating between views:
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left, R.anim.slide_in_left, R.anim.slide_out_right);
ft.replace(R.id.main_linearlayout_fragmentcont, frag);
ft.addToBackStack(null);
ft.commit();
I have run into a number of problems when rotating and the activity is reconstructed. I need to support old versions of android so android:configChanges="orientation" isn't an option. A lot of the issues are due to the nature of how Android saves Fragment state.
These are the problems I am running into:
1) The Fragment transitions don't remember my custom animations for pop events when they are restored automatically after a rotate. They do however remember my BackStack. I know I can write my own back handler that does a replace using animations and get rid of pop all together but I was wondering if there is a way to either reset the animation before calling popBackStack() or a way to have the FragmentManager remember the animations when it auto restores after rotate.
2) The other issue I have is that I have a bunch of child views (linearlayouts) in one of my top level fragment views that contain their own fragments. These child views are created and populated programmatically. When my fragment is recreated after rotation, I programmatically reconstruct the child views in onCreateView of the Fragment Object and I end up with duplicate fragments under each of the child views (1 - I create programmatically and 1 - Android Fragments create from restore). I am assuming this is because I programmatically reconstruct the child views after rotation with the same id. Is there a way to prevent Fragments from being restored? When does Android inject the Fragments from savedState into these views I construct programmatically? How would I prevent this from happening?
3) The above replace code seems to fire onCreateView multiple times for my frag (Fragment) object. This is without rotation and happens when I run the above code only once. Is there a reason that onCreateView of a Fragment would be called multiple times with the above code?
Questions about Fragments:
1) Can I prevent Android from auto restoring fragments when an activity is reconstructed? How would I go about this? Is it based on the ID of the LinearLayout? Could I call removeAllViews of the LinearLayout containing the fragment onStop? That way the view doesn't exist when it saves?
2) Is there a way to add a Fragment to a LinearLayout that I have a reference to but that doesn't have an ID? It appears the Fragment add, replace APIs require an int ID.
Thanks!
1) if you find out how let me know, I'm also pissed off by that
2) you're probably calling add on the FragmentTransaction inside the top level fragment, but the restore operation is also adding, so duplicates! option 1. Use replace instead. option 2. (preferred) Check if(savedInstances==null) { // do transaction } else { //let the system rebuilt it itself}
3) If you're changing the layout (by calling add or replace) of a view that is a part of a fragment, the manager call the method to creates the view again. I'm still not sure if that is a bug or a feature, and if it's a feature why it is. If you find out let me know
1) (supposed to be 4, no?) don't mess with the layouts, if u want to remove, remove them using while(popBackStackImmediatly){}, but if you go deeper and understand what the system is doing, usually there's no reason to not let it do it automatically.
2) (supposed to be 5, no?) if you have a reference you have the id View.getId()
happy coding!
If you are change the orientation of device then check the validation in activity and it also manage the fragment with stack so your flow not damage in that case.
if(savedInstanceState == null) {
mFragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction =
mFragmentManager.beginTransaction();
FragmentOne fragment = new FragmentOne();
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();
}