Cannot get Fragment data because of Fragment Life cycle - android

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.

Related

Unknown fragment after rotation

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)

When will a dynamically added fragment be re-added?

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
}

Correctly removing fragment when finished

When switching between multiple fragments is it necessary to start a new FragmentTransaction and remove each fragment when finished or, for example, if references to the fragments are stored as instance variables (to provide access to fragment methods from the host activity) is setting the instance variables to null when the fragment is replaced sufficient?
I am concerned if each fragment is not removed that it somehow lives on at least until a new fragment with the same id is added to replace the old one.
Once the fragment is replaced with another fragment it will call the onDestroy method of the replaced fragment.
is setting the instance variables to null when the fragment is replaced sufficient?
There is nothing already in there when you replace the fragment and when you call it, let say a getter method it will return null because onDestroy already clean out all the data that was inside of that fragment.

What is the correct way to restore a fragment?

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.

onCreateView() isn't called immediately after FragmentTransaction.commit()

I have an activity where I dynamically replace fragments:
private void goToFragment(Fragment newFragment, String tag) {
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.fragment_container, newFragment, tag);
ft.addToBackStack(null);
ft.commit();
}
Now, I want to access the views inside the fragment so I can put data (that I have stored in my activity) into them, immediately after calling goToFragment.
The problem is, the fragment's onCreateView isn't called before the fragment is rendered completely, at least to my understanding.
I know overriding the onAttach(Activity activity) in the fragment is one way to go about it, but then I have to cast it specifically to my activity - and I just want to avoid that because I consider it bad practice for the fragment to be dependent on a specific activity.
As far as I can see, Fragment doesn't have any listeners (as a subject) implemented.
So I figure I have to make my own listener (Using the Observer Pattern to make the fragment a subject and the activity an observer), and then calling it whenever the onCreateView or onAttach is done, and then finally calling back to the fragment with the data that needs to be set. However, I need to do this for several fragments so I would have to make a listener for each fragment, which I again think is bad.
Is there any better/easier way to do this?
FragmentTransaction isn't applied instantly after calling commit(). You may force update manually:
...
mFragmentManager.executePendingTransactions();
AFAIK event callbacks' purpose is custom communication with Fragment beyond it's usual lifecycle.
The correct way to do it would be to define an interface for Activity classes wishing to display your Fragment should implement. That way, on onAttach you don't cast to a specific Activity but to your interface.
See for instance: http://developer.android.com/guide/topics/fundamentals/fragments.html#EventCallbacks
You should use onActivityCreated to set the values.
Set references in onCreateView and then set values to them in onActivityCreated.

Categories

Resources