In my application I create a fragment with the keyword new and set it by FragmentTransaction.
Upon rotation a stumbled upon a NullPointerException in the method onActivityCreated() indicating a missing injection, that I do after the call to new. I suspected the fragment was not created by my code und proved this by logging the hashCode(). It looks like a fragment is created automatically by the system upon rotation.
Where does it come from?
Is it created by the fragment manager?
How am I supposed to use it correctly?
How can I access it, to set the missing value?
For now I ignore it by testing for the null value, in which case onActivityCreated() does nothing. Instead use the fragment I create with new. However, this does not feel very satisfying, to throw away an object, that was already created.
Where does it come from? Is it created by the fragment manager?
On Activity recreation, Android will restore the fragments which already exist in activity's fragments manager
How am I supposed to use it correctly?
public void onCreate(Bundle savedInstanceState){
if(savedInstanceState == null){
//activity is created for first time
//commit the fragment
}else{
//Activity is recreated(by means of rotation or something else)
//Dont commit the fragment, fragmet will be restored by the system
}
}
How can I access it, to set the missing value?
Normally, you have to handle this inside the fragment using onSaveInstanceState method. You can get the fragment instance by using, getSupportFragmentManager.findFragmentById(R.id.container) or getSupportFragmentManager.findFragmentByTag(tagName)
Related
I'm currently creating an app using a bluetooth connection. The connection is handled inside an simple object (HandleConnection). When the connection is made, my app goes into "remote" mode, which means I'm replacing my main fragment with another one (RemoteFragment) which use the "HandleConnection" object.
Until the everything is going well, since at the end of my "onCreate" (inside my activity) I set the RemoteFragment's handleConnection attribute:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
if (savedInstanceState == null) {
retainedFragment = new RetainedFragment();
getFragmentManager().beginTransaction().add(retainedFragment, RetainedFragment.class.toString()).commit();
handleConnection = new HandleConnection(this, handler);
retainedFragment.setHandleConnection(handleConnection);
} else {
remoteFragment = (RemoteFragment) getFragmentManager().findFragmentByTag(RemoteFragment.class.toString());
retainedFragment = (RetainedFragment) getFragmentManager().findFragmentByTag(RetainedFragment.class.toString());
if (remoteFragment == null)
remoteFragment = new RemoteFragment();
handleConnection = retainedFragment.getHandleConnection();
remoteFragment.setHandleConnection(handleConnection);
}
fragmentTransaction.commit();
}
Everything works fine except when I turn into landscape mode.
Then (after playing in debug mode), it seems my RemoteFragment is recreated after my setter is called, which obviously mean my handleConnection attribute is null.
Why is it doing that ? I would understand if I didn't reuse the previous fragment (then I would have two different fragments on top of each other) but in this case it makes non sense.
I made a workaround by calling a callback function inside the onCreateView to get the HandleConnection object, but why would the attribute be null when I called a setter right before ?
Thanks !
A Fragment's life cycle is directly affected by the host activity's life cycle. when the activity is destroyed, so are all fragments. During Layout changes, an activity is destroyed and recreated.
In this case, after rotation, your else condition in the onCreate() method will execute and hence the behavior that you are getting. Have a look at the documentation for thorough understanding.
http://developer.android.com/guide/components/fragments.html
You need to add to your activity registration in AndroidManifes.xml something like that: <activity android:configChanges="orientation|screenSize"/>
Read this article
So apparently my problem was that I wasn't using the right instance of my RemoteFragment, which means my current reference (in my activity) had the correct non-null value for my object, but the fragment displayed wasn't this one (it was the old one which was recreated and thus had a null value for the handleConnection).
The Fragment documentation shows an example of an activity dynamically adding a fragment in onCreate(...):
if (savedInstanceState == null) {
// During initial setup, plug in the details fragment.
DetailsFragment details = new DetailsFragment();
details.setArguments(getIntent().getExtras());
getFragmentManager().beginTransaction().add(android.R.id.content, details).commit();
}
This almost makes sense to me, except for one detail. I thought the reason for checking savedInstanceState == null was that if the activity is being re-created, we can expect the framework to re-add the fragment for us. However, I thought the framework would only do this if the fragment has a tag, and the example uses the version of FragmentTransaction#add(...) that does not take a tag. So as I understand it, if this activity were recreated, it would not have a DetailsFragment.
Is my understanding wrong? And if the framework does re-add the fragment, at what point in the activity's lifecycle is it guaranteed to have done so?
I thought the framework would only do this [re-add the fragment] if the fragment has a tag
No. According to the documentation in "Adding a fragment to an activity" section:
each fragment requires a unique identifier that the system can use to restore the fragment if the activity is restarted.
Thus, you have 3 possibilities to handle this: using an unique id, unique tag or none of them. Yes, neither of the previous two requirements.The purpose of adding a tag is to capture the fragment. This is useful if you want to retrieve it easily and play with it (such as performing transactions). However, it's not required.
When you use add(int, Fragment), it calls add(int, Fragment, String) with a null tag at the 3rd parameter. Therefore, the system will use the ID of the container view instead (1st param in add()). Thus, the fragment is restored without any id or tag that you supplied but correctly handled by the system.
Note: in reference of add(int, Fragment, String), you can see this quote: "Optional tag name for the fragment, to later retrieve the fragment with FragmentManager.findFragmentByTag(String)" - How could it be optional if we need it to restore fragments? ;)
At what point in the activity's lifecycle is it guaranteed to have done so?
I cannot honestly respond on it, maybe someone has the right answer but, here's what I think.
As you can see in "Handling configuration changes", when orientation (or any specific change) occurs on activity, it calls onDestroy() and directly onCreate(). At the point of (activity's) onCreate() is called, the child fragment will receive a callback in onAttach(). Beside, when the activity has received its onCreate() callback, a child fragment receives automatically a onActivityCreated() callback. After that, each method in activity will trigger the "exact" same child methods (onStart(), onResume(), onPause(), onStop()).
Thus, I think the safe point is onStart(), because activity triggers directly the call of the fragment's method, you're sure that the fragment is attached to it and you can handle onRestart() -> onStart() to update the UI.
I don't use tags with my Fragments and mine work just fine, I have the exact same test as you.
if (savedInstanceState == null) {
//fragment needs to be created
}
if (savedInstanceState != null) {
//fragment will automatically be there with no code
}
First of all I'm sorry if this explanation seems unclear, I'm new to Android.
I have a ViewPager in main activity showing fragments added dynamically by user. Fragments are created initially on activity start up and are added to ViewPager via Adapter i.e. adapter simply returns proper fragment and as I understand correctly fragment's content is created at this time when ViewPager 'retrieves' a fragment first time.
The problem is when main activity gets restored after orientation changing all fragments are resurrected as well and when Adapter tries to return newly created by user Fragment method createView() is no longer called and it fails with NullPointerException. It seems ViewPager retains fragments attached to it initially and doesn't call createView() for newly added ones for the same position.
I have a feeling I'm missing vital point on the Fragment lifecycle. I wouldn't like to change the design. My main question is what the correct way is to return a Fragment added to ViewPage after activity is restored? Is there any way to locate recently attached fragments?
If the fragment already exists, it will be re-used. However the state of the fragment will not. You should take a look at http://developer.android.com/training/basics/activity-lifecycle/recreating.html for more information.
In particular you should look at overriding onSavedInstanceState and onRestoreInstanceState as a method to repopulate the field that is generating that nullpointerexception
To determine if an instance of a fragment has already been created you can use 'findFragmentByTag' like so:
String fragmentTag = MyFramgment.getClass().getName();
MyFragment frag = getFragmentManager().findFragmentByTag(fragmentTag);
if(frag == null)
frag = new MyFragment();
Whatever ends up being referenced in 'frag', show this to the user.
Using Tabs means using fragments, and for some reason fragments have new steps in their life cycle, like onAttach(Activity).
My fragment fills up some maps from the resources, and it is done on onAttach() instead of the fragment constructor; because in the constructor getResources() throws an exception due to the lack of Activity yet.
The fragment is created on MainActivity like this:
#Override
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
switch( tab.getPosition() ) {
case 0:
if( fragmentTab0 == null ) {
fragmentTab0 = new MyFragment();
setTabText(0, ((MyFragment)fragmentTab0).getMyName());
}
fragmentTransaction.add(R.id.fragmentContent, fragmentTab0, "TAB0");
break;
Here lies my problem, in the call to the fragment method getMyName() which uses the maps I mentioned before to get a string. The call to getMyName() is executed before the fragment's onAttach() and the maps are not ready yet.
I am sure I can find a convoluted way to get the name (actually I tried already to pass the activity to the fragment's constructor and built the maps there, and it works, but it goes against the fragment philosophy).
I would have thought that the activity should be visible during Fragment constructor, since the fragments are created from the activity they're going to be eventually attached, so there is no point in delaying the activity attachment.
I also would have thought that the call to new MyFragment() should return after onAttach() is done. But it returns right after the constructor is done.
Therefore I feel not comfortable with the situation and I wonder if I am using fragments the wrong way, if so, the question is, how am I supposed to do it right to be able to call getMyName() there.
Note: From the fragment life cycle diagram it is clear that onAttach() and onDetach() are indistiguishable from onCreate() and onDestroy() respectively, so I question if they are really necessary.
onAttach() is not invoked until the fragment transaction is committed. Until then, a fragment has no reference to the creating Activity unless you pass it such a reference. Passing that reference is probably the cleanest way to implement what you're trying to do.
Generally in Android the title is dictated by the containing activity, not the fragment. For example, a PreferenceActivity's xml headers file lists titles and their associated fragments; those titles do not appear in the preference xml files used by the PreferenceFragment.
Here's the issue I'm having. I've got an activity A that has a fragment F, which is contained in FragmentPagerAdapter FPA, which is in view V. (A->V->FPA->F)
When A gets destroyed (or in this case, swapped out), F is attached, and is in FPA, which is in V. However, when A gets recreated (someone hits the back button back into the activity, for instance), V and FPA don't exist, so F is recreated (in the attached state!), but to something that doesn't exist, so it's not in the view hierarchy at all. Then, when FPA tries to instantiateState on this fragment, it'll try to attach it, which does nothing because it's already attached to thin air.
There are obviously a few ways to fix this (have V and FPA exist in onCreate of the activity, so that the fragment has somewhere to go when it gets created, for instance), but I'd like to continue to lazily create FPA and V only when needed.
Thusly, it seems like updating the state of F to detached in onDestroy() would be desirable. However, state is saved in onPause(), which means I'm kinda out of luck here.
Is there a way to update the saved state of F in A's onDestroy()? Is there a way to say "don't rehydrate this fragment if the activity gets destroyed"? Is there some other obvious way of thinking about this that I'm not considering? It feels like I'm going about things the wrong way here.
I've had my trouble with FragmentPagers. What I do is passing a null bundle in the Activity onCreate() and then create everything from scratch every time it is created. Like so:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(null);
// do my stuff
}
This way the fragment wont be passed on when the activity is recreated.
You wrote:
Is there some other obvious way of thinking about this that I'm not considering?
This doesn't directly answer your title question but provides a convenient solution to how to preserve Fragment state in a ViewPager.
You can save the Fragment states when the Activity is destroyed by tagging the Fragment in the Activity that initializes the Fragment:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.photos_pager_activity);
MyImageFragment fragment;
if (savedInstanceState != null) {
fragment = (MyImageFragment) getFragmentManager().findFragmentByTag("my_image_fragment_tag");
} else {
fragment = new MyImageFragment();
getFragmentManager().beginTransaction().add(android.R.id.content, fragment, "my_image_fragment_tag").commit();
}
See also:
Uses of fragment tags
ViewPager and fragments — what's the right way to store fragment's state?