I load a bunch of Child fragments dynamically inside a Parent's Fragment linearLayout (fragmentContainer), then when user clicks a button, I need to remove them all and add new ones. I don't know the exact number of fragments that will be added each time. This is my way of removing all the fragments at once
LinearLayout ll = (LinearLayout) view.findViewById(R.id.fragmentContainer);
ll.removeAllViews();
Now I can add the new ones using fragment transaction methods.
This way of removing all fragments is super easy and works for me better than removing each fragment one by one using remove() But is it a good practice? How about performance? Do you recommend a better way?
This is my way of removing all the fragments at once
No, it isn't. It is your way of removing all views from that container.
This way of removing all fragments is super easy and works for me.
It does remove any fragments. It removes views. That is why the method is named removeAllViews().
But is it a good practice?
No. For starters, when you rotate your device or undergo a configuration change, you will notice that all your "removed" fragments come back.
Do you recommend a better way?
Keep track of the outstanding fragments (e.g., using an ArrayList<Fragment>), then iterate over that list, passing each to a remove() method on a FragmentTransaction.
If you simply want to remove all fragments, code below:
FragmentManager fm = getActivity().getSupportFragmentManager();
for(int i = 0; i < fm.getBackStackEntryCount(); ++i) {
fm.popBackStack();
}
This code is good only if you use the add method of FragmentTransaction when referencing fragments. Method popBackStack is used for removing.
In Kotlin you can do:
repeat(fragmentManager.backStackEntryCount) {
fragmentManager.popBackStack()
}
Where fragmentManager could be one of:
Activity.supportFragmentManager
Fragment.requireActivity().supportFragmentManager
Fragment.childFragmentManager
Fragment.parentFragmentManager which replaced requireFragmentManager()
I would like to suggest you: Use Kotlin and fragment-ktx using this one:
In your gradle:
implementation "androidx.fragment:fragment-ktx:$androidXFragmentKtxVersion"
In your Fragment
childFragmentManager
.findFragmentByTag(YOUR_TAG)?.let { fragment ->
childFragmentManager.commit(allowStateLoss = true) {
remove(fragment)
}
}
If you are not using childFragmentManager use requireActivity().supportFragmentManager instead
requireActivity().supportFragmentManage
.findFragmentByTag(YOUR_TAG)?.let { fragment ->
requireActivity().supportFragmentManage.commit(allowStateLoss = true) {
remove(fragment)
}
}
Related
I'm learning about fragments I have some doubts. Consider following code:
FragmentManager fm = getFragmentManager();
Fragment MyFragment = new Fragment();
fm.beginTransaction().replace(R.id.my_container, MyFragment).addToBackStack(null).commit();
My question is:
what exactly does replace do?
What happens if I create many fragments this way (to replace previous ones in a container).
Can it in any way be bad for memory usage?
Is it considerably better just to change fragment's content?
Replace removes all the fragments that are in the container and adds the new fragment to the container. (if there isn't a fragment in the container then it just adds the new one).
If you create many fragments this way then every transaction is saved to the backstack so you can reverse the transaction by pressing the back button.
The only thing you can do is to create a variable fragmentTransaction and use the fm.beginTransaction() only once and not every time you want to replace the fragment in the container.
I don't think so, fragments should be modular and reusable.
You can read more here:
https://developer.android.com/guide/components/fragments.html
it simple put another "layer" on container.
appcrash
yes
No, fragment is the easiest way.
Using fragment & backstack tag to reference to a Fragment if you want to call fragment again and process Back button.
fm.beginTransaction().replace(R.id.my_container, MyFragment, "FRAGMENT_TAG").addToBackStack("FRAGMENT_BACKSTACK_TAG").commit();
I am using FragmentManager to dynamically add fragments to an activity. How can I add a new fragment before another one instead of just at the end without having to redo the entire set of fragments?
For example, on the activity there are three fragments shown vertically like this:
FragA
FragB
FragC
When I add a fourth fragment NewFragD they should now be arranged like this:
FragA
NewFragD
FragB
FragC
You need something like this:
#Override
public Fragment getItem(int position) {
if (position == 0) return new frg1();
else if (position == 1) if (newFragment)return new Mapfrag2(); else return frg2()
else if (position == 2) return new frg3();
else return new frg1();
}
I have had a similar issue and I don't think it's possible to reorder fragments already committed.
The way I fixed this issue that I found to be better than clearing and re-adding the fragments in the right order is to have all the potential fragments added in the correct order and then show/hide when they are ready to be displayed.
So in your example, you would initially have:
transaction.add(FragA)
transaction.add(FragD)
transaction.add(FragB)
transaction.add(FragC)
and then this line before committing the transaction
transaction.hide(FragD)
and when it's time to show the frag you would create another transaction and add this line
transaction.show(FragD)
This is better since all the fragments wouldn't have to go through the lifecycle again. I've found that using show/hide instead of trying to add a fragment to the transaction results in clearer and more performant code.
This will only work if you have a fixed order for all potential fragments, if you need to dynamically change the order then you would resort to clearing and adding fragments in the proper order each time you need to update it.
I've found a number of questions that are similar, but I haven't found any answers that seems to fit my specific case
What is the proper way of iterating through all of the fragments on the backstack, in order to perform a specific operation on each of them? I need to update some and remove some of the fragments, based on changes in the environment (I could, theoretically, trap an event when the fragment receives focus again and take appropriate action then, but this would complicate things a bit, as I'm also dealing with things getting renamed)
Given that the container for the fragments is as follows:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/fragment_container"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
And adding fragments as follows:
FragmentTransaction transaction = context.getSupportFragmentManager().beginTransaction();
Fragment fragment = new CustomFragment();
transaction.add(R.id.fragment_container, fragmentBase);
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
transaction.addToBackStack(nameOfFragment);
transaction.commit();
context.getSupportFragmentManager().executePendingTransactions();
My first attempt to iterate was:
getSupportFragmentManager().executePendingTransactions();
for (int indexFragment = 0; indexFragment < getSupportFragmentManager().getBackStackEntryCount(); indexFragment++)
{
FragmentManager.BackStackEntry backStackEntry = getSupportFragmentManager().getBackStackEntryAt(indexFragment);
// If Android keeps the ID, surely there should be a way of getting to the actual fragment itself, from that ID?
Fragment fragment = getSupportFragmentManager().findFragmentById(backStackEntry.getId());
if (fragment != null)
{
//TODO: Cast the fragment and perform some transactions against it
// We never get here; as fragment is always null
}
}
From a different angle:
FrameLayout frameLayout = (FrameLayout)findViewById(R.id.fragment_container);
for (int indexFrameLayout = 0; indexFrameLayout < frameLayout.getChildCount(); indexFrameLayout++)
{
View view = (View)frameLayout.getChildAt(indexFrameLayout);
// I get the right number of views; but I can't interact with them as fragments
}
I could, hypothetically, hack my way past this issue by keeping references to the fragments, but there are problems with that approach.
Is there a way that I can get to the fragments that are currently on the backstack?
Given the assumption that I can get to a fragment, is there a way of removing it (and it alone) from somewhere within the backstack?
(There is the transaction.remove method, but was that intended to work on fragments sitting in the middle of the back stack?)
Thanks
BackStackEntry is not necessary associated with single Fragment, it represents a FragmentManager state, you may have a bunch of fragments in a transaction and therefore a bunch of fragments in a state. Use this flavor of add() method and assign an unique id to your fragments via tag. Then you'll be able to find them via getSupportFragmentManager().findFragmentById(fragment_tag);
As for your task, afaik there's no way to remove an arbitrary state from BackStack, your only choice is to pop stack removing one state after the other from the top. So you can, for example, override your onBackPressed() and call fragmentManager.popBackStack() to simply go to the previous state or fragmentManager.popBackStack(backstack_tag) to skip some states and go where you need to.
I'm finally looking into the new nested fragments APIs in the support library revision 11.
Everything It worked pretty well till I tried to use the activity reference held by the nested fragments.
After a configuration change the childFragment doesn't seem to get detached and re-attached to the new activity.
Basically after an orientation change my childFragment is in an inconsistent state from which I can't get the correct activity instance with getActivity().
I manged to get the correct one using getParentFragment().getActivity() and it works, but I don't think that's the right way to go.
here is the code I use to add the fragment in the parentFragment the first time, after that the fragment is automatically added back to the parentFragment:
public void addChildFragment() {
Fragment f = getFragment().getChildFragmentManager().findFragmentByTag( FRAGMENT_CHILD_TAG );
if (f == null) {
FragmentTransaction ft = getFragment().getChildFragmentManager().beginTransaction();
f = new TrackBrowserFragment();
f.setArguments( getFragment().getArguments() );
ft.add( R.id.fragment_album_detail_child_fragment_layout, f , FRAGMENT_CHILD_TAG );
ft.commit();
}
}
This inconsistent in the activity instance obviously lead to multiple problem with my fragment ( binds with services, broadcast receivers and so on ).
I'm probably doing something wrong cause I don't think that this is the correct behavior of a nested fragment.
so:
Am I doing something wrong with the code?
Is this the expected behavior of a nested fragment?
Am I missing something?
Should I detach/attach it by myself?
Thanks
I found out wich was the problem, i was using setRetainInstance(true) in the parent fragment and that kept the child fragment to be detached.
After I Removed that line everything works as expected
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();
}