Being frustrated about fragment behavior, I started doing some testing.
I have one activity and 2 fragments. Fragment A is declared inside the xml layout of the activity and Fragment B is added (only if it's not present) in the layout of the activity in activity's onCreate() method. I've added logging in all of the main lifecycle methods for the activity and the 2 fragments and tested the behavior when switching orientation back and forth. Here are my findings:
Fragment B (the dynamically-added fragment) behaves as expected:
a) after an orientation change, the savedInstanceState bundle contains what has been previously saved in onSaveInstanceState()
b) if setRetainInstance(true), during an orientation change, onDestroy() is not called and also the subsequent onCreate() is not called. The fragment's fields are preserved during the orientation change
Fragment A (the fragment defined in the xml layout) doesn't behave as expected:
a) after an orientation change, the savedInstanceState bundle is always null although onSaveInstanceState() has been properly called
b) if setRetainInstance(true), during an orientation change, onDestroy() is not called as expected BUT, contrary to what is expected, onCreate() is also called when the fragment is being reattached. And also, the fragment's fields are not preserved.
To sum up, for fragments declared inside xml layouts and using ACL v4, saving state during orientation changes does not work and setRetainInstance(true) does not work.
My question is if someone tested this functionality on Android 3.0+ and can say if fragments work correctly when using fragments from Android SDK.
One workaround to this problem would be to always dynamically create my fragments. Did anyone find a different workaround?
Revision 4 of ACL fixed these issues.
Related
I wonder if there is any advantage of fragments, on screen rotation.
Generally fragments get destroyed followed by activity. Is there something that fragments retain while doing so?
onDestroy() method is called both in the activity and fragments.
I can try to figure out advantage of Fragment on Screen rotation.
Realtime app problem is:
Android is the potentially frequent destruction and reconstruction of an Activity. The most common time this occurs is when the user rotates the device between horizontal and portrait orientations (Screen rotation).
This crashing usually occurs because device orientation changes cause the Android framework to tear down the displayed Activity along within any contained Views, and then to fully reconstruct the Activity/View hierarchy. Any references to the Activity or to the Views within the Activity suddenly become invalid. Similarly any references within the Activity or Views that were set as a result of a user action or similar are now lost.
There are a number of ways to deal with this issue but one of the easiest is to take advantage of Fragments.
Things to keep in mind:
Fragments won’t automatically resolve this issue because, by default, when the Activity is torn-down in response to an orientation change the Fragment contained within the Activity is also torn down along with any contained Views.
The solution lies in an underused method: Fragment.setRetainInstance with a value of true.
why?
Calling setRetainInstance with a value of true causes Android to preserve the Fragment across the teardown/reconstruction cycle of an Activity. Along with the Fragment, the Views or other object references contained within the Fragment or Views remain.
With setRetainInstance(true) called on a Fragment instance.when an orientation change occurs, Android…
Holds a reference to the Fragment instance
Tears down the old Activity instance
Creates a new Activity instance
Attaches the preserved Fragment instance to the new Activity instance
you must add
android:configChanges="keyboardHidden|orientation|screenSize"
in parent Activity also calling
setRetainInstance(true)
in onCreate of fragment
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 using fragments in my app. Below is the screenshot of my app. Everything looks fine on activity launch. But, when I change the screen orientation, the layout overlaps itself while scrolling. Any ideas what is wrong?
EDIT (Solution):
I found out what was wrong. A new fragment was added on orientation change. I was previously using fragmentTransaction.add(...), I replaced it with replace method.
For future vistor's understanding...
You are most likely adding your fragment in your activity's onCreate().
When your device is rotated, your activity is destroyed and re-created, but it also recreates any added fragments.
With these two things in mind...
onCreate is called and you add a fragment to your activity. You have one fragment.
You rotate the screen.
The activity is destroyed and re-created. Your fragment is destroyed and recreated. You still have one fragment.
Your activity calls onCreate() as a part of its recreation, which adds a fragment to your activity. You now have two fragments.
If one or both of your fragments have transparent backgrounds, both may display at once, as is likely the case here.
Either use replace instead of add (which you have done), or better, only add the fragment in onCreate() if savedInstanceState is null. If it is null it means it's the first onCreate() call.
I have a tab host control that is loading tabs using fragments.
Each time a tab is switched it detaches the old fragment and attaches the new fragment.
I noticed that the OnCreateView method is called during this process, and that a lot of my state is getting lost since it recreates the view each time. However I noticed that some view state such as the value of edit text is being maintained across detach/attach.
I'm wondering how Android is automagically restoring state when the view is being completely destroyed and recreated as a new view. The value of the Bundle savedInstanceState is always null when I am just switching tabs. Bundle savedInstanceState only becomes populated when I do something like rotating the screen.
as far as I can tell this restoring of state is taking place just before the fragment onStart method is called.
When attaching and detaching fragments only the views are destroyed, the fragment instance remains the same.
The fragment manager restores the states of views that have ids, and the savedInstanceState is null.
In case of rotation, the fragments are probably recreated by you somewhere else (in activity's onCreate()?).
When a fragment is about to be removed from the window (or replaced) its onSaveInstanceState(Bundle) (or onRestoreInstanceState(Bundle)) method is called. This will propagate through the fragments hierarchy restoring its previous state.
I am using fragments in Android. At the beginning the API seemed very easy to use. Now I can say that it is overcomplicated with many problems that are not clearly documented. For example following many tutorials, I have created two layout one for the portrait and one for the landscape. In the portrait I put just one fragment (list) while in landscape I also have the detail fragment.
If I rotate the device, onCreateView of detail fragment gets called (and it is ok). The problem is that, if I rotate again the device in portrait (with no detail fragment in the xml), the onCreateView of Detail fragment is called again! Why this? and how to avoid? I can check for ViewGroup container == null but it is terrible.
UPDATE: making some research, I found that if the fragment is in the layout with the tag this is called just once when the layout is not present while if you replace a FrameLayout present just in one layout with the fragment, then the onCreateView is always called. I was expecting it was not called if attached to a container that is not present
Thanks