When rotating the screen my nested fragment is shown but for some brief moments, the parent fragment is also shown.
I have my MainActivity that has a FrameLayout with ID activity_base_container.
I'm doing this when my activity starts:
Fragment initialFragment = getInitialFragment();
mFragmentManager.beginTransaction()
.add(R.id.activity_base_container, initialFragment, initialFragment.getClass().getSimpleName())
.commit();
That initialFragment initial fragment is responsible to check some conditions and depending them will launch one of two possible fragments:
fragmentManager.beginTransaction().replace(R.id.activity_base_container, fragment, fragment.getClass().getSimpleName()).commit();
Lets assume it launches FragmentF (whit a root FrameLayout with id fragment_f_root). This fragments layout has a set of options. When the user clicks one of those options, the corresponding fragment is created and is launched like this:
//The example here is an option that displays a google map.
fragment = FragmentMapMultipleActivity.newInstance();
fragmentManager.beginTransaction()
.replace(R.id.fragment_f_root, fragment)
.addToBackStack(fragment.getClass().getSimpleName())
.commit();
At this point all is working as expected. The problem is when I rotate the screen. FragmentF appears briefly and then immediately FragmentMapMultipleActivity, the nested fragment, appears.
Is it possible after rotating the screen show only the nested fragment or I should change my "architecture" to something else?
should change my "architecture" to something else?
Probably, you should.
The brightest Android-minds from Square are even advocating to avoid simple fragments everywhere it's possible: Advocating Against Android Fragments
Nested fragemnts, in its turn, increase complexity exponentially. The only good pattern of using them I've seen so far is ViewPager with it's FragmentPagerAdapter. In majority of other cases, consider using Custom Views instead.
It keeps your app's lifecycle cleaner and more predictable.
I don't think you can do much with this blinking you see, apart from:
setRetainInstance(true) and avoid full re-creation of the Fragment in Activity, so you keep you fragment's data during change of the configuration (and then pass same retained fragment to the fragment manager)
keeping layouts as lightweight as possible
avoid re-creation of already initialized variables
keep onViewCreate() as lightweight as possible
Good luck!
Related
My main fragment has too many views to load because the lines of code in the file are increasing. To avoid this I decide to separate views using a child fragment. So now upper views are in the child fragment and the remaining bottom views are in the main fragment. Till this ok.
Now I am opening a new fragment by clicking one view from the main fragment. When I came back to the main fragment it is reloading the child fragment because of that I am getting NullPointerException and the app crashed.
Following is the way I am adding child fragments.
childFragmentManager.beginTransaction()
.replace(R.id.fragment_container, fragment)
.commitAllowingStateLoss()
For more understanding.
I am using Navigation with BottomNavigationBar.
How to avoid this?
In some cases, fragment views are flickering when back to that fragment. How to avoid that?
For your first question you want to avoid putting the fragment in the backstack.
childFragmentManager.beginTransaction()
.replace(R.id.fragment_container, fragment)
.addToBackStack(null)//<-- Here
.commitAllowingStateLoss()
For your second question Fragments are destroyed to conserve memory,typically happens when you can't see it. As such the recreation process could take some time, depending on how heavy the view is, thus the blinking.
The only way to stop it from blinking is to make sure the Fragment isn't doing so much that it can't seamlessly load back in.
You could do this by lazy loading your more heavy views, like lists, videos, and large images.
You could also look into Fragment transitions. These could visually smooth out the process of loading.
Here is a great source for some standard animations.
Fragment transaction animation: slide in and slide out
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();
I have a Fragment, MainFragment, which can contain two, three, or four nested fragments. The specific fragments that will be shown can be changed by the user in the settings.
There is a different layout for each number of fragments. For instance, layout_3 is used when the user chooses three nested fragments.
What I need to do is dynamically update MainFragment's layout, and which fragments will be nested within that layout, in onResume() (i.e. Once the user comes back from the settings). There are about 10 fragments the user can choose from, and I need to be able to swap them in and out of MainFragment dynamically.
I'm having trouble doing this. The only way to update the layout/view once I return from the settings is to leave MainFragment and then come back (which calls onCreateView()).
Here is an example of what I do in onCreateView() to initialize the layouts (two nested fragments is the default):
mView = mInflater.inflate(R.layout.layout_2, mParent, false);
getChildFragmentManager().beginTransaction().add(R.id.fragmentContainer1, fragment1).commit();
getChildFragmentManager().beginTransaction().add(R.id.fragmentContainer2, fragment2).commit();
return view;
Suppose the user then goes to the settings and chooses to have three nested fragments. This is what I've tried in onResume(), to no effect:
mView = mInflater.inflate(R.layout.layout_3, mParent, false);
getChildFragmentManager().beginTransaction().add(R.id.fragmentContainer1, fragment1)).commit();
getChildFragmentManager().beginTransaction().add(R.id.fragmentContainer2, fragment2).commit();
getChildFragmentManager().beginTransaction().add(R.id.fragmentContainer3, fragment3).commit();
I'm not sure if I'm doing something wrong. Ideally, I would just force MainFragment to call onCreateView() again, but none of the solutions for that problem seem to work.
Any ideas? Thanks for the help.
Edit: I believe the problem is with inflating the new View, rather than replacing the fragments.
For instance, suppose the default screen is layout_4, with four fragment containers. The user then goes to the settings un-checks all four default fragments, and chooses three new fragments. In onResume(), we try to inflate layout_3, and then add the fragments. I think layout_3 never inflates, but because my fragment containers have the same style id across layouts (i.e. fragmentContainer1 - fragmentContainer4), the first three fragment containers are updated. The fourth one remains as it was, since I assumed we were in layout_3 and did not try to update it.
This behavior is confirmed and results in a crash when the user tries to increase the number of fragments, rather than decrease. Above, when the user switched from four fragments to three fragments, there was no crash because all three fragment containers I tried to update exist in layout_4. But if the user is in layout_2 and then goes to the settings to select a third fragment, we'll try to add a fragment to fragmentContainer3 when we resume. This results in a crash because layout_3 fails to inflate.
java.lang.RuntimeException: Unable to resume activity
java.lang.IllegalArgumentException: No view found for id 0x7f0c005f
Any ideas how to fix this? The call to re-inflate mView in onResume() does not seem to have any effect.
Edit 2: I've tried calling mParent.addView(mView) after inflating, but still experience the same behavior as above, for the most part.
When you return from the settings, onResume() should be called in the MainFragment and subsequently any nested fragments that were already loaded in the MainFragment. Can you not include any update logic in the nested fragments' onResume() instead of only in onCreateView()?
Otherwise you can create a different code path and put update logic there: make public methods in the classes for fragment1, fragment2, fragment3 that include all your update logic, and call those methods from somewhere in MainFragment. (You could create an interface and have the nested fragment classes inherit that interface, if they are different classes and you want to have a cleaner design.)
Be careful about whether or not the nested fragments have been resumed yet—calling methods on View objects when nested fragments' onResume() hasn't been called yet could be problematic.
I am writing an android app that will have a number of different screens that I would like to swipe between, each screen will be a full page except for action bar header. On each screen there is the ability to open up another screen which will also be multiple screens that I would like swipeable. What is the best way to handle this. Do I have one fragment manager that holds all the screens and handle the onPageScrollStateChanged to only allow swipes between the current accessable screens or would I be best off nesting the fragments. I hope the above makes sense.
Thanks in advance
Sounds like you want to use a ViewPager to to swipe between views (Fragment extends View)
You could either:
In a single activity use a FragmentManager that switches between the parent and child Fragments, each with their own ViewPager and nested Fragments
Start a new activity to hold each ViewPager
Both are valid, if the Fragments need to communicate with each other or the Activity option one might suit the project needs better.
For the swiping between views you indeed need a ViewPager
For the nested fragments I would use a wrapper. I struggled a lot with fragments and found that this is the best way. A wrapper is very simple. This is just a fragment that holds other fragments. In the onCreate() of this fragment you get the childFragmentManager and add the fragment you originionally wanted to add. If you want to go to a new fragment you simply get the childFragmentManager again and replace the current item. This way you have nested fragment. You can add this to the backstack in order to get back navigation, but you need to override onBackPressed() inside your activity and call the method popBackStack() from the fragmentManager in order to get the first fragment back.
If you have any questions, comment below.
Support4Demos feature Fragment stacks... but conveniently place the pop/push CountingFragment controls outside of the stack (all takes place in one layout, but the push/pop buttons are below, still in the Fragment that holds the whole layout.
How can the controls be made to work from the top fragment?
[EDIT]
Another way to look at it:
I want to make Fragments on a stack behave like Activities, each can pop itself, or launch another.
How would you alter the "FragmentStack" example from Support4Demos so that the "Go home"/"Add new"/"Pop top" buttons are in the CountingFragments, rather than in FragmentStackSupport which holds the FragmentManager?
A reasonable design - consider "CountingFragments" containing clickable ListViews?
How to do that? Ideally, each stack Fragment would hold a reference to the "stack holder' Fragment, or its (Child)FragmentManager. Those, however, are not Parcelable, and Fragment constructors must not be used (though everything else works great when I do it). Holding a reference to that is also bad, since the "stack holder' Fragment, too, can be destroyed and recreated.
[EDIT2]
All right, here are some ideas I came up with.
For "go home" and "pop top", I suppose I could add those (invisible) buttons to the hosting Fragment, and then access them in the hosted Fragments via getWindow().getDecorView().findViewById or somesuch.
That's ugly enough. But pushing new ones? Only related thing I can think of is adding a custom View class into the hosting layout, that holds a reference to the FragmentManager, to be grabbed by the hosted Fragments.
holding references to the FragmentManager(s) in a static map somewhere
register a BroadcastReceiver in the hosting Fragment, send broadcasts with data from the hosted ones, telling it what to do. But the host Fragment shouldn't have to babysit its children.
Have each stacked Fragment itself be host to either its true content, or the next stacked Fragment. That way, there is always a FragmentManager at hand (except for popping), but 2 fragments are needed per page, not to mention the weirdness of it all.
Please tell me there is a cleaner way.