Nested fragments and back button cause duplicate id - android

I have an activity which hosts 3 fragments in a ViewFlipper. Each of those three fragments hosts fragments of their own. I am using the ViewFlipper like a tab control, which allows me to very simply switch between various "views" in the app. This all works fine, so far.
When the user is inside a view, there is a navigation flow. I use:
final FragmentTransaction txn = getChildFragmentManager()
.beginTransaction();
txn.replace(R.id.view1_silo_container,
new View1Fragment());
txn.addToBackStack(null);
txn.commit();
to move around inside this view. So as the user navigates, I call some variation of the code above to replace the current fragment with a new one. Again, this all works fine so far.
The problem is that, when I get to the bottom fragment (A>B>C) and then I hit the back button to go from (C>B) I get a duplicate id error. The problem is that the "B" fragment itself has a fragment nested in it. As long as I avoid giving this fragment an id, there is no problem. However, if I give this fragment an id then I get "Duplicate id, tag null, or parent id 0x0 with another fragment".
I don't understand why this is a problem, and I haven't found a way to work around it. Am I doing it completely wrong? Is there some small piece I am missing?

The answer is:
Note: You cannot inflate a layout into a fragment when that layout includes a <fragment>. Nested fragments are only supported when added to a fragment dynamically.
This can be found in the Android 4.2 APIs documentation about the new nested fragments feature.
Once I removed the <fragment> from the layout and used getChildFragmentManager() to insert the fragment 'manually' in onCreateView, it worked fine. No more duplicate errors.

Related

Which Layout to use with Fragments?

Recently delve into fragments and from what I understand to create a fragment you need a java class and the fragments layout. This makes sense. However what I cant seem to wrap my head around is what "container", or layout do I use to store/insert the fragment? In android studio you can use this to insert fragments, or you can use any other of the layouts. But which one is ideal to use?
Also I saw in a reddit post that I shouldn't be using fragments at all and that its preferred to instead use Frame layouts and play around with your views visibility for the desired effects. Is this true?
You're slightly over-complicating the concept of Fragments.
Fragments, like Activities, don't actually need a dedicated fragments layout xml file. If you choose to do so, you can create the entire layout through Java code, not that I will understand why you'll choose to do so.
So for Fragments, you don't need a Java class and a fragment's layout file. The only requirement is the Java class, and the layouts file is just the preferred approach to inflating a layout, similar to how it is for Activities.
As for your question about the container of the fragments, it's really a matter of your app's design.
You can add a Fragment to your Activity or other Fragments, through the FragmentManager in code or through the <fragment> tag in your layout.xml files.
Neither of those are the best way or the preferred way, since it really depends on what your app needs.
Using the <fragment> tag will cause that fragment to always be added whenever the layout is inflated. This is actually VERY bad if your Activity requires dynamically switching Fragments due to your use of things like ViewPagers, Tabs, Drawer Navigation, or etc. However, it's GREAT if there's no need to dynamically switch fragments and for that specific Activity or parent Fragment, this fragment is a fragment that's always loaded.
For example, let's say you designed a flexible AddNew Fragment that's used in a Dialog and an AddNewActivity. Due to reusing the same screen and code, you decide to make this part of your code a fragment so you can insert it inside a DialogFragment or into another Activity. But, for those DialogFragments and Activities, the only Fragment it'll have is the AddNewFragment, so it'll make sense to just insert that fragment into the Dialog layout and Activity layout through the <fragment> tag.
As for the option with Java code, the preferred approach is to use a FrameLayout. But there's no need to play around with any View visibilities!
The common approach is to just use:
<FrameLayout
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
A FrameLayout is used because it's going to be the container of the Fragment. In other words, the Fragment will be stored inside of this layout.
So in Java code, you can simply use this code to replace the Fragment inside the container with your new one:
getSupportFragmentManager().beginTransaction().replace(R.id.container, AddNewFragment.newInstance()).commit();
Optionally, you can use add() instead of replace() if you want the fragment to be placed ontop of another fragment within the FrameLayout container.
So yes, to give a decisive answer to your question, there's no ideal way to add a Fragment to an Activity or another Fragment. Each option has it's benefits and drawbacks, with some working better for certain situations and others working better for others.
In the end, it really depends on what your App needs. If you need your Fragments to be flexible, so you can switch Fragments, then this must be done through Java code, because fragments added through the <fragment> tag can't be removed at runtime. However, if you don't need your Fragment to be replaced and it's definitely always going to be showing the same Fragment, then using the <fragment> tag removes the need to write extra Java code to load the dedicated Fragment.
One thing I really do need to point out is... that reddit page you read about is wrong. The 'preferred' way to use Fragments is not to use FrameLayouts and play around with View visibilities. I actually have no idea why there's even a need to change View visibilities.
You can use the new androidx.fragment.app.FragmentContainerView to get a better performance than FrameLayout.
Here more information.
You could use the Fragment layout, but this isn't very flexible. If you're just showing one Fragment, it should work, but using a FrameLayout and inserting your Fragment into it works better as this allows you to change them on the fly.
You may see a FrameLayout with the id R.id.container or something similar, and what this is used for is the Transaction.
For example, if you want to insert FragmentOne into your layout, you can just do this and it'll put it into R.id.container.
Fragment fragment = new FragmentOne();
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.replace(R.id.container, fragment);
transaction.commit();

Does adding fragments with tags cause them to not be destroyed when their parent is destroyed?

I have an android application that uses one activity and a number of fragments to build a ui dynamically
The base activity has a simple LinearLayout in it that has nothing (BaseActivity)
I add a fragment to it that only contains a drawer layout with an actionbar a framelayout and a navigation bar (BaseFragment)
To that I add one of two fragments, one shows all children as lists (SerialFragment) , the other in a wizard style (WizardFragment)
Each of those can add one (in case of a wizard) or many (in case of a list) fragments (QuestionFragment)
When I navigate away from BaseActivity then the BaseFragment's onDestroy() gets called, but not the onDestroy() of any of the child fragments
I add the child fragments like so
FragmentTransaction trans = parent.getFragmentManager().beginTransaction();
set_default_animation(trans); //this just adds a custom animation
trans.replace(R.id.wizard_content, QuestionFragment.NewInstance(),question_id.toString()); //in case of a wizard
trans.add(R.id.list_content,QuestionFragment.NewInstance(), question_id.toString());//in case of a list
trans.commit();
The only difference between the BaseFragment (which gets destroyed) and the others, is that in the others I add a tag to them (section_id or question_id) so that I can retrieve them using findFragmentByTag.
Yet when the user navigates away from the activity only BaseFragment's onDestroy() is called.
Is the reason for this the fact that , that fragment is the only one I haven't added using a tag?
Note that I am not using a support fragment manager, the normal one, so I use tags to locate the fragments I want , since the non-support fragment manager does not contain getFragments().
Also note that I have not set the retain instance flag to true on any of the above mentioned fragments
I could test the above by removing tags and adding references to the fragments on the parents, but that means a LOT of refactoring which I would like to avoid if that is not the problem.
Thanks in advance for any help you can provide
After refactoring everything, it seems that the tags weren't the problem (though I still removed them)
What fixed it for me, is using getChildFragmentManager() instead of getFragmentManager() to update the gui
That way when the base view gets destroyed its children are removed aswell
Hope it helps someone

Closing FrameLayout for a Fragment in Android App

I'm working on a simple Android App as a self-learning Project. I've got a lot of it functioning, and I have a main Activity which has a FrameLayout and some RecyclerView and FloatingActionButton stuff going on inside of it.
However, I want to make one of my buttons in my NavigationDrawer open a different view in the FrameLayout using Fragments. Is there a way to do this, sort of making a new Fragment for the RecyclerView and the other stuff and putting the RecyclerView and FloatingActionButton in there?
I tried doing something like this (when the appropriate NavigationDrawer button was clicked):
statsFragment = new StatsFragment();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.rootLayout, statsFragment);
transaction.addToBackStack(null);
transaction.commit();
But this caused my app to crash. Any pointers?
Is your R.id.rootLayout a Layout or a Fragment component?
If your main Activity has a hard coded Fragment (by hard coded i mean declared in your XML), then no, you can't change a hard-coded fragment content with FragmentTransaction.
More information on this question's answer.
Finally, you can't put your components from one Activity inside a new Fragment/Activity since each component belongs to one and only one Context.
See, Activity extends Context. When you get a reference to a component declared in your XML by using Activity.findViewById(int), you are actually making a pointer to a determined View from this Activity's layout, defined in your onCreatemethod with setContentView(id). You can't take this pointer and throw it to another Fragment or Activity within your application without expecting any problems. (Well, maybe you can, but it's totally against the best practices of Android Programming).

Android - Re-inflating a fragment leads to "Error inflating class fragment"

I'm all new to Android Development but already in big trouble. I've read several threads both here and on other sites regarding a similar or even the same problem. But all of the answers I've found don't fit my needs or aren't working for me at all.
So here's the thing. I'm having a single Activity with a self-made "menu" layer including 3 buttons at the bottom of the activity and a ScrollView above. Now on clicking one of those buttons I simply inflate a fragment into the ScrollView using
svContent.removeAllViews();
getLayoutInflater().inflate(R.layout.fragment_xyz, (ScrollView) findViewById(R.id.svContent));
which is inflated perfectly fine.
Now the inflated fragment includes another fragment for Google Maps (API 2), which looks like this.
<fragment
android:id="#+id/fMap"
android:name="com.google.android.gms.maps.MapFragment"
android:layout_width="fill_parent"
android:layout_height="150dp" />
This fragment is also populated just the way I want on its parent fragment's inflation and the map is working fine.
Now I click on another menu button to have another fragment being inflated. But right when I click the button for the first fragment again, I get the following error:
Duplicate id xxxxx, tag null, or parent id xxxxx with another fragment for com.google.android.gms.maps.MapFragment
Like I said, I've tried several solution I found online including
moving from extending Fragment to FragmentActivity
using SupportMapFragment instead of Fragment
inflating the sub-fragment (the Google Maps one) from code instead of XML
All of these had several disadvantages I cannot accept at the moment (at least I think) or "didn't work" for me or in my constellation
Any help is greatly appreciated. And please me know if you need more information.
Regards,
D.
To replace the fragment in your ScrollView :
SupportMapFragment mapFragment = MapFragment.newInstance();
FragmentTransaction ft = getChildFragmentManager().beginTransaction();
ft.replace(R.id.svContent, _mapFragment );
ft.commit();
If you want to go back to the previous frgament with the back button, add ft.addToBackStack(null); before the commit.
And that's it.
By the way, you should probably not have a ScrollView as the container of your fragments. If the content must be scrollable, put the scroll in your fragment and put the fragment in a LinearLayout/FrameLayout/RelativeLayout.
To handle fragments, you should use the FragmentManager. This means that you should not inflate a fragment directly. Rather, you should allow the FragmentManager to do it for you.
you need to make map to null when user navigates to another screen.
destroy google map instance when onDestroy method.
#Override
protected void onDestroyView() {
Fragment fragment = getFragmentManager().findFragmentById(R.id.fMap);
getFragmentManager().beginTransaction().remove(fragment);
gMap.clear();
gMap = null;
super.onDestroy();
}

FragmentPagerAdapter inside Fragment

I'm having a bit of trouble implementing a design based around multiple ViewPagers.
At a high level, I have a FragmentActivity with just a FrameLayout as it's content. I have 3 different Fragments that I want to display. All 3 are full screen and only 1 will be used at a time.
Fragment 1 is a basic fragment with some TextViews and ImageViews.
Fragment 2 has a ViewPager and a FragmentPagerAdapter that feeds it several simple fragments.
Fragment 3 has a ViewPager and a FragmentPagerAdapter that feeds it several simple fragments (that are different from Fragment 2)
In my FragmentActivity onCreate() I get the FragmentManager and begin a transaction to replace whatever is in my FrameLayout with a new instance of Fragment 2.
At this point everything is working as expected. My ViewPager in Fragment 2 works perfectly.
Now I have a menu option that replaces the Fragment 2 in my FrameLayout with a new instance of Fragment 3. This also works fine.
The problem arises when I try to put Fragment 2 back into the FrameLayout with another replace transaction. I see my PagerIndicater at the top, but my pages are blank.
I've tried just creating a new instance of my Fragment 2 and calling a replace transaction. I've also tried setting a tag on my Fragments when I call replace and adding a findFragmentByTag check before my replace instead of creating a new instance. Both gave me the same result of blank pages after my second replace.
For reference
My first design was simply a FragmentActivity with a ViewPager and a ViewIndicater. I only had Fragment 2 and Fragment 3 from my description above and a menu option to switch between them. To switch I had 2 different FragmentPagerAdapters defined and just called ViewPager.setAdapter to set the selected FragmentPagerAdapter. This was working perfectly, but now I need a new top level Fragment that isn't using ViewPager at all. This is why I decided to move my ViewPagers out into their own Fragments. My idea being that I would just swap in my fragments to a FrameLayout.
I don't have my code in front of me right now so I can't post any, but I'll add some code to my question tomorrow to help facilitate answers.
This question is a possible duplicate of Navigating back to FragmentPagerAdapter -> fragments are empty.
If your app can handle it (API 17), use getChildFragmentManager(). This problem seems to occur when using a Fragment to host your ViewPager and using FragmentPagerAdapter. Changing to FragmentStatePagerAdapter seemed to fix the problem as well, but I still think using getChildFragmentManager() is the smartest thing to do.
brockoli you can used not "good way". But for my it's worked.
You can use in view 2 layouts, where you bind fragments. First - for fragment with fragments. Second - for other fragments.
On replace fragment with fragments do not replace, but only change visibility first layout to gone and add new fragment to second layout. On back, remove fragment from second layout and set visibility for first layout to visible.

Categories

Resources