I am writing an app that uses Fragments for the UI, very similar to the Notepad sample code. It has a List of items then an editor for these items. In landscape mode the list and editor fragments are positioned side by side, but in portrait mode only the list is shown in the main activity, and the editor is shown by launching a new activity.
Within my editor fragment I have calls to findViewById to get at the individual components of the UI (mostly EditText fields). Under normal conditions this all works fine, but when the screen is rotated from landscape to portrait and the main activity is destroyed and created again it seems to call onActivityCreated (plus other callbacks) on my editor fragment even though it is not part of the display now (since in portrait mode it is just the list that is shown).
The problem is, findViewById to get the UI components returns null (I'm guessing because the fragment is not being displayed). But why are the calls being made at all? It is making me need null checks everywhere to make sure I don't try and use the UI components when they aren't on screen.
when the screen is rotated from landscape to portrait and the main activity is destroyed and created again it seems to call onActivityCreated (plus other callbacks) on my editor fragment even though it is not part of the display now (since in portrait mode it is just the list that is shown).
Correct. Android recreates all existing fragments during a configuration change. Or, if the fragment was added using a FragmentTransaction and you used setRetainInstance(true), the exact same fragment object is retained during a configuration change.
But why are the calls being made at all?
See above.
The problem is, findViewById to get the UI components returns null
You only need to call findViewById() in onCreateView(), and there you know whether the "UI components" exist, because you are creating them yourself.
It is making me need null checks everywhere to make sure I don't try and use the UI components when they aren't on screen.
Then use isVisible() or one of the other is...() methods on Fragment. Or, use setRetainInstance(true) so your fragment is not destroyed and recreated. Or, remove the Fragment (e.g., in onCreate() of the activity, if the passed-in Bundle is not null and you are now in portrait).
Related
I have an Activity which can open 2 different Fragments by 2 different Buttons. By the default that Activity when it creates, it is opening a Fragment, we call it "The Main Fragment".
The first Fragment to which we are going over by the first Button we meet zero problems with the Rotation, but the second one after the rotation disappears and the screen shows the Main Fragment's content. When I tried to rotate the screen back, I see the Main Fragment's content again. But why it is so, if I didn't write any code, which must return me to the Main Fragment without clicking a button.
What assumptions do you have?
Why this is happening ?
Default Behavior, Actiivty is getting recreated on orientation change so your fragment are.
Explanation
You need to understand Activity Life Cycle to understand why this is happening.
First, “rotating the screen” is not the actual scenario we are talking about today. Because any configuration change will cause Android to restart your Activity. A configuration change might be the device rotating (because now we have a different screen layout to draw upon), or it could be a language switch (because we need to re-write all those strings, which may need more room now OR it could be the scary RTL switch!), or even keyboard availability.
By reloading your app, what the system is actually doing is calling onDestroy() and then immediately calling onCreate(). This way, your Activity is as fresh as possible, with all of the right creation data (even though the user has been with you the entire time).
Now you have following option -
Either Fix Orientation for your app from AndroidManifest.xml
But oviously that is not a very good experience for user.
Save activityState with onSaveInstanceState()
This method will be called before onDestroy(). And, when your Activity is created, there’s a matching step onRestoreInstanceState(), which will also be called automatically. All of these automatic steps mean that you can let the system worry about saving and loading your data, because you planned ahead and mapped out what was important. (Or, you can skip onRestoreInstanceState() and load your saved state from the Bundle that comes with onCreate().
In you integrate Fragment in activity, because activity is getting destroy() so your fragment will also destroy() and will be recreated.
Please take a good read on Handling Configuration Change and this.
Once you understood the concepts things will start falling into your but it will only happen if you will complete your learning curve.
Happy Coding !
That is because onCreate is being called every time the screen is rotated. Probably you are displaying The Main Fragment from your onCreate method. You will face the same issue if you put your fragment display logic in onResume because just after onCreate, onResume is called.
Solution: store the fragment on top in shared preferences that way you know what to display every time onCreate is being called.
I have viewPager with adapter where I instantiate fragment and return it in getItem() method. So when Im on the first page I have only 2 fragments alive. When I rotate the phone, they both get destroyed and garbage collected (system calls their onDestroy() and onDetach() and I can see them in .hprof memory dump as ready to be garbage collected). However, when a new activity is created with new viewpager and 2 new fragments, for some reason, system creates 2 more fragments for those that were destroyed previously and attaches them to the activity, while not adding them to viewPager at all. They just sit in fragmentManager's mActive and mAdded arrays. When I rotate device few times, it creates these new instances each time. Now, this may seem like a leak that causes fragments not to be garbage collected but they indeed are. All these fragments have different #xxx number and are atached to a new activity within a new fragment manager. I've tried everything, any help or suggestions are much appreciated, thanks!
ViewPager handles the lifecycle of the Fragments it shows. As you mention it creates the visible Fragment and the ones next to it (if used with a FragmentStatePagerAdapter) to be able to smoothly swipe into them when the time comes. When the fragment is out of sight, it can be destoryed and recreated by the ViewPager at its will.
The default beheavior for a rotating screen is that the activity is recreated together with the ViewPager and the Fragments.
However this can be changed.
With android:configChanges="orientation" in the manifest you can prevent the activity of being recreated upon orientation change.
The other way of preserving the fragment instance upon an orientation change is to call setRetainInstance(true) in the fragment to be retained.
So I it was (unexpectedly:D) bad code. I had in my Activity's onRestoreInstanceState() piece of code that was creating and setting new adapter and so it put old fragments in background. Sorry for bothering with unanswerable question.
How exactly v4.app.Fragment works? I have viewPager with 7 Fragments. I was sure that when moving between fragments, depending on the situation each fragment can be automatically destroyed and created. However it seems not to be true.
I decided to play a bit with debugging and set some flag for my Fragment class object, something like
class MyClass extends Fragment {
public boolean myFlag=false;
When I set ex. true somewhere in the code which is run only once (ex. true is set after button click), it seems that this value is true until the app ends. Thus it suggest the object is kept all the time in memory.
However when user moves between Fragments onCreateView and onViewStateRestored methods seems to be called.
And now I feel quite confused. If Fragments are not destroyed, why those methods are executed by Android?
Do I have the guarantee that my Fragment object will be kept always with all fields (when user only moves beteween Fragments and doesn't leave the app)?
If not how should I save and restore its state? public void onSaveInstanceState(Bundle savedInstanceState) method seems to be only run when user left the app, not when user moves between Fragments.
Have you seen any good tutorial concerning ViewPager and Fragments?
And now I feel quite confused. If Fragments are not destroyed, why
those methods are executed by Android?
If you look at this diagram, Fragments do not need to be destroyed to call onCreateView().
Do I have the guarantee that my Fragment object will be kept always
with all fields (when user only moves beteween Fragments and doesn't
leave the app)?
This depends on what you are doing. If your case is dealing with ViewPager it really boils down to what PagerAdapter you use and your ViewPager configuration.
If not how should I save and restore its state? public void
onSaveInstanceState(Bundle savedInstanceState) method seems to be only
run when user left the app, not when user moves between Fragments.
It depends under what conditions you are wanting to restore state. For example, for orientation changes you can call setRetainInstance(true) on the Fragment and the Fragment's state will be preserved when there is a configuration change given your Fragment is not on the backstack.
Have you seen any good tutorial concerning ViewPager and Fragments?
The Android Guide has a good tutorial.
You can re-initialize all the values of a specific fragment in it's onCreate(...) method. So that when ever it is navigated to, it will have the default values that you setup in your onCreate(...) method.
Here are some useful links related to the Implementation of Fragments in a ViewPager:
1. http://thepseudocoder.wordpress.com/2011/10/13/android-tabs-viewpager-swipe-able-tabs-ftw/
2. http://androidtrainningcenter.blogspot.co.il/2012/10/viewpager-example-in-android.html
3. http://manishkpr.webheavens.com/android-viewpager-example/
4. http://thepseudocoder.wordpress.com/2011/10/05/android-page-swiping-using-viewpager/
I hope this helps.
I have a ListFragment which depends on the hosting Activity to properly initialize. On first run, it loads up fine. Once I change the orientation, my app crashes. From the stack trace I can see it isn't me trying to add the Fragment prematurely, rather Android is trying to restore the Fragment.
I have setRetainInstance(false) set in the onStart method but can't find any method to disable the restoring of the Fragment once the orientation changes. Any ideas? Do I need to remove the Fragment prior to my app being destroyed?
Edit: I ended up delaying initializing the list until the Activity is ready. Android conveniently shows a 'loading' message until the adapter is set.
I ended up delaying initializing the list until the Activity is ready. Android conveniently shows a 'loading' message until the adapter is set.
I have a ListFragment which depends on the hosting Activity to properly initialize.
That may be your difficulty right there.
From the stack trace I can see it isn't me trying to add the Fragment prematurely, rather Android is trying to restore the Fragment.
Correct.
Any ideas?
I would focus on handling configuration changes properly. Between onSaveInstanceState() in the fragment and the combination of onRetainNonConfigurationInstance(), getLastNonConfigurationInstance() (both on Activity) and onAttach() (on your Fragment), you should be able to pass whatever stuff is in the old fragment to the new one without crashing. For configuration changes, do not rely upon "the hosting Activity to properly initialize".
I have an UI where I need a Fragment to be displayed (with a view) in landscape mode but not in portrait mode. In portrait mode it should still be available but will display its result using ListPopupWindow instead.
I figured I could handle this by using the <fragment /> tag for the landscape layout while creating the fragment programmatically if it wasn't started (in the case when we are in portrait).
This works fine as long as you start out in landscape, if you start in portrait where the fragment is created programmatically your application will crash when you rotate the emulator when it tries to attach the fragment to your layout:
java.lang.IllegalStateException:
Fragment did not create a view.
at
android.app.Activity.onCreateView(Activity.java:4095)
The docs for Fragment.isInLayout() seems to hint that it should be able to handle it this way:
Return true if the layout is included
as part of an activity view hierarchy
via the tag. This will
always be true when fragments are
created through the tag,
except in the case where an old
fragment is restored from a previous
state and it does not appear in the
layout of the current state.
So the question is how to do this correctly or if there is something I'm missing?
UPDATE:
Seems like isInLayout() isn't behaving as it should currently as well. It returns false if you have added a Fragment to a container manually.
Also, if you add a Fragment manually to a container and then rotate (the device) to a layout that does not contain that layout it will crash:
Caused by:
java.lang.IllegalArgumentException: No
view found for id 0x7f060011 for
fragment SearchFragment{4042f868 #2
id=0x7f060011 SearchFragment} at
android.app.FragmentManagerImpl.moveToState(FragmentManager.java:722)
Have you come up with an answer to this? I was having a similar problem, and managed to come up with a solution. You can easily do what you are attempting as follows:
Create two different layouts one in the layout directory, one in the layout-land directory. The one in the layout-land directory will be used in landscape mode. As a placeholder, where you want your fragment to go, use s FrameLayout element, and id it, say with the id "my_fragment". The layout in the layout directory should not contains any element with that id.
In your onCreate method, use findViewById(R.id.my_fragment) to locate the fragment placeholder. If it exists, you are in landscape mode and should add your fragment (if it does not exist already): add(R.id.my_fragment, new MyFragment, "myFragment). If you get null, you are in portrait mode and should not create the fragment.
Be very careful that you never replace a fragment created using a tag, with one that you create dynamically in your program. A fragment for which isInLayout returns true is a completely different beast, that one for which it returns false. Their lifecycles are entirely different. Replacing one with the other will lead to the dreaded IllegalStateException "Fragment did not create a view" problem.
-blake
Your problem can also be due to not having a lanscape layout for the fragment you are using. You might have one for the portrait and so your program runs fine but when you rotate your device, the OS probably looks for the view in the landscape folder and doesnt find the view so declares it as missing. Check that you have view both in the folder "layout" and "layout-land".